From 90b5604f491a558537d8a72f45ea0f4666721b01 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 03:11:14 +0000 Subject: [PATCH] Deploy to GitHub pages --- 2015/08/01/knowledges.html | 1230 ++++ 2015/08/02/jekyll-basic-usage.html | 1265 ++++ 2015/08/02/shell-string.html | 1297 ++++ 2015/08/03/init.html | 1205 ++++ 2015/08/05/Supercolliding-a-PHP-array.html | 1292 ++++ 2015/08/07/prepare.html | 1454 ++++ 2015/08/09/varying-number-of-arguments.html | 1357 ++++ 2015/08/10/gnu-autotools.html | 1541 ++++ 2015/08/11/python-file-read.html | 1268 ++++ 2015/08/12/sql-join.html | 1252 ++++ 2015/08/18/YII2-Behavior.html | 1212 ++++ 2015/08/18/YII2-component.html | 1280 ++++ 2015/08/21/PHP-object-pass-by-referance.html | 1197 ++++ 2015/08/21/Yii2-YiiBase.html | 1271 ++++ 2015/08/22/PHP-array.html | 1195 ++++ 2015/08/25/Yii-Model.html | 1192 ++++ 2015/08/25/Yii-Vector-Dictionary.html | 1192 ++++ 2015/09/10/C-io-fcntl.html | 1192 ++++ 2015/09/10/Javascript-default-arguments.html | 1212 ++++ 2015/09/10/Javascript-variables-type.html | 1264 ++++ 2015/09/11/libevent-tiny-introduction.html | 2254 ++++++ 2015/09/14/Memcached-earliest-version.html | 1358 ++++ 2015/09/17/memory-alignment.html | 1708 +++++ 2015/09/17/sql-on-duplicate-key-update.html | 1189 ++++ 2015/09/19/how-to-read-source-code.html | 1333 ++++ 2015/09/26/APUE-buffer.html | 1205 ++++ 2015/10/16/memcached-init.html | 2091 ++++++ 2015/10/17/craftyjs-getting-start.html | 1313 ++++ 2015/10/31/vim-as-a-php-ide.html | 1238 ++++ 2015/11/04/Memcached-socket-init.html | 1408 ++++ 2015/11/05/Memcached-slabs.html | 1724 +++++ 2015/11/06/Memcached-event-handler.html | 1200 ++++ 2015/11/07/Memcached-item.html | 1533 ++++ 2015/11/08/Memcached-assoc-xxx.html | 1437 ++++ 2015/11/13/Memcached-threads.html | 1693 +++++ 2015/11/14/Memcached-locks.html | 1419 ++++ 2015/11/28/YII2-AJAX-Form-Submission.html | 1259 ++++ 2015/11/28/yii2-pjax.html | 1500 ++++ 2015/11/30/convert-man-page-to-pdf.html | 1203 ++++ 2015/12/25/jump-function.html | 1237 ++++ 2015/12/26/graphviz-dot.html | 1401 ++++ 2015/12/26/python-grammer.html | 1525 ++++ 2015/12/30/yii2-getting-start.html | 1242 ++++ .../30/yii2-making-custom-app-with-yii2.html | 1251 ++++ 2016/01/06/reactjs-tutorial.html | 1996 ++++++ 2016/04/14/makefile.html | 1196 ++++ 2016/08/16/raining-day.html | 1204 ++++ 2017/01/09/learning-mongodb.html | 1296 ++++ 2017/02/07/Redux.html | 1434 ++++ 2017/02/11/Redux-advance.html | 1362 ++++ 2017/02/26/lisp-basic.html | 1453 ++++ 2017/03/10/json-c.html | 1233 ++++ .../python-official-extension-tutorial.html | 1645 +++++ 2018/01/24/bitcoin-note.html | 1301 ++++ 2022/07/09/backtrader-quick-start.html | 2522 +++++++ 2022/08/13/shift-register.html | 2049 ++++++ 2022/08/31/arduino-schematic.html | 1314 ++++ 2022/08/31/avr-gcc-tutorial.html | 1192 ++++ 2022/10/12/homebrew.html | 1258 ++++ 2022/10/12/letsencrypt.html | 1222 ++++ 2022/10/12/virtualenv.html | 1251 ++++ 2022/10/28/arduino-ov7670.html | 1585 +++++ .../28/hardware-document-reference-copy.html | 1219 ++++ 2023/03/24/sed-and-awk-101-hacks.html | 1709 +++++ 2023/04/14/meta-programming.html | 1336 ++++ 2023/04/14/metaclass.html | 1742 +++++ 2023/06/29/monkey-patching-in-go.html | 1543 ++++ .../15/time-sequence-trending-algorithm.html | 1511 ++++ ...ear-regression-decision-tree-trending.html | 1387 ++++ 2023/09/20/decison-tree.html | 1488 ++++ 2024/01/25/vue-web-component.html | 1396 ++++ ...Learning-Mathematical-Foundations-hw2.html | 1511 ++++ 404.html | 1086 +++ CNAME | 1 + about.html | 1153 +++ archive.html | 1543 ++++ assets/android-chrome-192x192.png | Bin 0 -> 6672 bytes assets/android-chrome-512x512.png | Bin 0 -> 19472 bytes assets/apple-touch-icon.png | Bin 0 -> 2082 bytes assets/browserconfig.xml | 9 + assets/css/main.css | 6200 +++++++++++++++++ assets/css/main.css.map | 1 + assets/favicon-16x16.png | Bin 0 -> 655 bytes assets/favicon-32x32.png | Bin 0 -> 931 bytes assets/favicon.ico | Bin 0 -> 15086 bytes assets/images/logo/logo.svg | 8 + assets/mstile-144x144.png | Bin 0 -> 2908 bytes assets/mstile-150x150.png | Bin 0 -> 2896 bytes assets/mstile-310x150.png | Bin 0 -> 3150 bytes assets/mstile-310x310.png | Bin 0 -> 6221 bytes assets/mstile-70x70.png | Bin 0 -> 1939 bytes assets/safari-pinned-tab.svg | 38 + assets/search.js | 1 + assets/site.webmanifest | 19 + favicon.ico | Bin 0 -> 15086 bytes feed.xml | 2833 ++++++++ index.html | 1317 ++++ nbmd/conf.json | 6 + nbmd/index.md.j2 | 101 + page2/index.html | 1425 ++++ page3/index.html | 1393 ++++ page4/index.html | 1246 ++++ page5/index.html | 1255 ++++ page6/index.html | 1260 ++++ page7/index.html | 1215 ++++ page8/index.html | 1224 ++++ page9/index.html | 1256 ++++ robots.txt | 1 + sitemap.xml | 324 + x | 1 + 110 files changed, 126360 insertions(+) create mode 100644 2015/08/01/knowledges.html create mode 100644 2015/08/02/jekyll-basic-usage.html create mode 100644 2015/08/02/shell-string.html create mode 100644 2015/08/03/init.html create mode 100644 2015/08/05/Supercolliding-a-PHP-array.html create mode 100644 2015/08/07/prepare.html create mode 100644 2015/08/09/varying-number-of-arguments.html create mode 100644 2015/08/10/gnu-autotools.html create mode 100644 2015/08/11/python-file-read.html create mode 100644 2015/08/12/sql-join.html create mode 100644 2015/08/18/YII2-Behavior.html create mode 100644 2015/08/18/YII2-component.html create mode 100644 2015/08/21/PHP-object-pass-by-referance.html create mode 100644 2015/08/21/Yii2-YiiBase.html create mode 100644 2015/08/22/PHP-array.html create mode 100644 2015/08/25/Yii-Model.html create mode 100644 2015/08/25/Yii-Vector-Dictionary.html create mode 100644 2015/09/10/C-io-fcntl.html create mode 100644 2015/09/10/Javascript-default-arguments.html create mode 100644 2015/09/10/Javascript-variables-type.html create mode 100644 2015/09/11/libevent-tiny-introduction.html create mode 100644 2015/09/14/Memcached-earliest-version.html create mode 100644 2015/09/17/memory-alignment.html create mode 100644 2015/09/17/sql-on-duplicate-key-update.html create mode 100644 2015/09/19/how-to-read-source-code.html create mode 100644 2015/09/26/APUE-buffer.html create mode 100644 2015/10/16/memcached-init.html create mode 100644 2015/10/17/craftyjs-getting-start.html create mode 100644 2015/10/31/vim-as-a-php-ide.html create mode 100644 2015/11/04/Memcached-socket-init.html create mode 100644 2015/11/05/Memcached-slabs.html create mode 100644 2015/11/06/Memcached-event-handler.html create mode 100644 2015/11/07/Memcached-item.html create mode 100644 2015/11/08/Memcached-assoc-xxx.html create mode 100644 2015/11/13/Memcached-threads.html create mode 100644 2015/11/14/Memcached-locks.html create mode 100644 2015/11/28/YII2-AJAX-Form-Submission.html create mode 100644 2015/11/28/yii2-pjax.html create mode 100644 2015/11/30/convert-man-page-to-pdf.html create mode 100644 2015/12/25/jump-function.html create mode 100644 2015/12/26/graphviz-dot.html create mode 100644 2015/12/26/python-grammer.html create mode 100644 2015/12/30/yii2-getting-start.html create mode 100644 2015/12/30/yii2-making-custom-app-with-yii2.html create mode 100644 2016/01/06/reactjs-tutorial.html create mode 100644 2016/04/14/makefile.html create mode 100644 2016/08/16/raining-day.html create mode 100644 2017/01/09/learning-mongodb.html create mode 100644 2017/02/07/Redux.html create mode 100644 2017/02/11/Redux-advance.html create mode 100644 2017/02/26/lisp-basic.html create mode 100644 2017/03/10/json-c.html create mode 100644 2017/05/18/python-official-extension-tutorial.html create mode 100644 2018/01/24/bitcoin-note.html create mode 100644 2022/07/09/backtrader-quick-start.html create mode 100644 2022/08/13/shift-register.html create mode 100644 2022/08/31/arduino-schematic.html create mode 100644 2022/08/31/avr-gcc-tutorial.html create mode 100644 2022/10/12/homebrew.html create mode 100644 2022/10/12/letsencrypt.html create mode 100644 2022/10/12/virtualenv.html create mode 100644 2022/10/28/arduino-ov7670.html create mode 100644 2022/10/28/hardware-document-reference-copy.html create mode 100644 2023/03/24/sed-and-awk-101-hacks.html create mode 100644 2023/04/14/meta-programming.html create mode 100644 2023/04/14/metaclass.html create mode 100644 2023/06/29/monkey-patching-in-go.html create mode 100644 2023/09/15/time-sequence-trending-algorithm.html create mode 100644 2023/09/17/linear-regression-decision-tree-trending.html create mode 100644 2023/09/20/decison-tree.html create mode 100644 2024/01/25/vue-web-component.html create mode 100644 2024/02/27/Machine-Learning-Mathematical-Foundations-hw2.html create mode 100644 404.html create mode 100644 CNAME create mode 100644 about.html create mode 100644 archive.html create mode 100644 assets/android-chrome-192x192.png create mode 100644 assets/android-chrome-512x512.png create mode 100644 assets/apple-touch-icon.png create mode 100644 assets/browserconfig.xml create mode 100644 assets/css/main.css create mode 100644 assets/css/main.css.map create mode 100644 assets/favicon-16x16.png create mode 100644 assets/favicon-32x32.png create mode 100644 assets/favicon.ico create mode 100644 assets/images/logo/logo.svg create mode 100644 assets/mstile-144x144.png create mode 100644 assets/mstile-150x150.png create mode 100644 assets/mstile-310x150.png create mode 100644 assets/mstile-310x310.png create mode 100644 assets/mstile-70x70.png create mode 100644 assets/safari-pinned-tab.svg create mode 100644 assets/search.js create mode 100644 assets/site.webmanifest create mode 100644 favicon.ico create mode 100644 feed.xml create mode 100644 index.html create mode 100644 nbmd/conf.json create mode 100644 nbmd/index.md.j2 create mode 100644 page2/index.html create mode 100644 page3/index.html create mode 100644 page4/index.html create mode 100644 page5/index.html create mode 100644 page6/index.html create mode 100644 page7/index.html create mode 100644 page8/index.html create mode 100644 page9/index.html create mode 100644 robots.txt create mode 100644 sitemap.xml create mode 100644 x diff --git a/2015/08/01/knowledges.html b/2015/08/01/knowledges.html new file mode 100644 index 0000000..56f6334 --- /dev/null +++ b/2015/08/01/knowledges.html @@ -0,0 +1,1230 @@ + + + +一些知识点 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

一些知识点

  + +
+
+ + +

安全

+ +
    +
  • 宽字符注入攻击
  • +
+ +

VIM

+ +
    +
  • Vim 折叠代码的用法
  • +
+ +

C 语言

+ +
    +
  • strtok函数详解
  • +
+ + + +

PHP

+ +
    +
  • PHP 名称空间: use namespace as name
  • +
+ +

PHP 内核

+ +

MySQL

+ +
    +
  • replace into使用
  • +
+ +

TODO list

+ +
    +
  1. C 语言信号文章
  2. +
  3. C 语言文件缓存处理(setbuf 函数)
  4. +
  5. C 语言命令行参数处理(getopt 函数)
  6. +
  7. C 语言资源相关文章
  8. +
  9. fcntl 操作文件描述符特性
  10. +
  11. 写一篇 set/getsockopt 的文章
  12. +
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/08/02/jekyll-basic-usage.html b/2015/08/02/jekyll-basic-usage.html new file mode 100644 index 0000000..1556edc --- /dev/null +++ b/2015/08/02/jekyll-basic-usage.html @@ -0,0 +1,1265 @@ + + + +Jekyll的基本用法 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Jekyll的基本用法

  + +
+
+ + +

什么是 jekyll??

+ +

简单说: 它是静态网页生成器. 具体点: 包含一些用 markdown 语法写的文件的文件夹;通 +过 markdown 和 Liquid 转换器生成一个网站. 同时 jekyll 也是 github 静态页面引擎. 意味着 +你可以用 jekyll 生成你的页面, 免费托管在 github 服务器.

+ + + +
+ +

快速开始

+ +

安装并生成目录

+ +

安装 jekyll.gem 执行文件可能需要其他依赖, 比如 ruby.

+ +
gem install jekyll
+
+ +

生成博客目录.

+ +
jekyll new myblog
+
+ +

基本用法

+ +

通过 gem 安装包管理器安装好 jekyll 以后, 就能够在命令行中执行下面的命令把项目编译 +到当前目录目录, 执行编译后, 目录下自动生成_site 文件夹

+ +
jekyll build
+
+ +

也可以使用--destination <destination>指定要编译到的地方, 使用 +--source <source>指定要编译的文件夹.

+ +

如果你还在写文章, 下面的命令可以自动监听文件变化, 自动编译

+ +
jekyll build --watch
+
+ +

注意: 编译到的目标文件夹会被清空.

+ +

预览

+ +

一切就绪之后, 就可以查看自己的第一篇文章啦.

+ +

启动服务器:

+ +

使用一下命令启动服务器

+ +
jekyl serve
+
+ +

然后浏览器访问: http://localhost:4000即可(预览).

+ +

注意:2.4 版本以后会自动检测文件的改变, 要禁止该行为, 请使用一下命令启动服务器

+ +
jekyll serve --no-watch
+
+ +

除了–no-watch 等配置项, 还有其他很多配置, 一般是放在根目录下面的_config.xml 文 +件下面, 前面的放在命令行也是一种方式.

+ +

调用 jekyll 命令的时候会自动用_config.xml 里面的配置. 比如: _config.xml 里的

+ +
source:_source
+destination:_deploy
+
+ +

相当于

+ +
jekyll build --source _source --destination _deploy
+
+ +

参考:

+ +
    +
  1. Jekyll 的設定和基本配置 (使用 TeXt 主題)
  2. +
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/08/02/shell-string.html b/2015/08/02/shell-string.html new file mode 100644 index 0000000..d657350 --- /dev/null +++ b/2015/08/02/shell-string.html @@ -0,0 +1,1297 @@ + + + +Shell中的字符串操作 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Shell中的字符串操作

  + +
+
+ + +

:=:-操作符

+ +
# 当a值存在时, :=与:-操作效果一样
+$ a='bc';str=${a:=aaa};echo $str, $a
+bc, bc
+$ a='bc';str=${a:-aaa};echo $str, $a
+bc, bc
+
+# str与a都被赋值
+$ str=${a:=aaa};echo $str, $a
+aaa, aaa
+
+# 注意下面, a没有被赋值
+$ str=${a:-aaa};echo $str, $a
+aaa,
+
+ + + +

%%%操作符

+ +

%%%操作符从右向左匹配, 并删除匹配的内容.

+ +
#!/bin/bash
+#提取文件名,删除后缀。
+
+file_name="text.gif"
+name=${file_name%.*}
+echo file name is: $name
+
+ +

输出结果:

+ +
file name is: test
+
+ +

${VAR%.*}$VAR中删除位于%右侧的通配符左右匹配的字符串, +通配符从右向左进行匹配. 现在给变量name赋值name=text.gif, +那么通配符从右向左就会匹配到.gif, 所有从$VAR中删除匹配结果.

+ +

%属于非贪婪操作符, 他是从左右向左匹配最短结果; +%%属于贪婪操作符, 会从右向左匹配符合条件的最长字符串.

+ +
file_name="text.gif.bak.2012"
+name=${file_name%.*}
+name2=${file_name%%.*}
+echo file name is: $name
+echo file name is: $name2
+
+ +

输出结果:

+ +
file name is: test.gif.bak    //使用 %
+file name is: test            //使用 %%
+
+ +

###操作符

+ +

###操作符从左向右匹配.

+ +
#!/bin/bash
+#提取后缀,删除文件名。
+
+file_name="text.gif"
+suffix=${file_name#*.}
+echo suffix is: $suffix
+
+ +

输出结果:

+ +
suffix is: gif
+
+ +

${VAR#*.}, 从$VAR中删除位于#右侧的通配符所匹配的字符串, +通配符是左向右进行匹配. 跟%一样, #也有贪婪操作符##.

+ +
file_name="text.gif.bak.2012.txt"
+suffix=${file_name#*.}
+suffix2=${file_name##*.}
+echo suffix is: $suffix
+echo suffix is: $suffix2
+
+ +

输出结果:

+ +
suffix is: gif.bak.2012.txt     //使用 #
+suffix2 is: txt                  //使用 ##
+
+ +

操作符##使用*.从左向右贪婪匹配到text.gif.bak.2012

+ +

示例

+ +

定义变量url="www.1987.name"

+ +
echo ${url%.*}      #移除 .* 所匹配的最右边的内容。
+echo ${url%%.*}     #将从右边开始一直匹配到最左边的 *. 移除,贪婪操作符。
+echo ${url#*.}      #移除 *. 所有匹配的最左边的内容。
+echo ${url##*.}     #将从左边开始一直匹配到最右边的 *. 移除,贪婪操作符。
+
+ +

输出:

+ +
www.1987
+www
+1987.name
+name
+
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/08/03/init.html b/2015/08/03/init.html new file mode 100644 index 0000000..ebfdff4 --- /dev/null +++ b/2015/08/03/init.html @@ -0,0 +1,1205 @@ + + + +PHP内核探索 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

PHP内核探索

  + +
+
+ + +

问, 为什么要读 PHP 源码?

+ +

个人读 PHP 源码纯粹是爱好, 顺便巩固自己学的 Linux C 编程知识, 当然了, 也为了在聊天 +室聊天的时候能显得比较牛逼… :)

+ + + +

简介

+ +

材料来源

+ +

我自己也是菜鸟一只, 这个系列的东西是网上的, 我在读的时候顺便 COPY 到我的 blog 里面, +是为了方便以后查阅(我个人的浏览器书签都几千条了, 如果放在书签里, 实在是不好找)

+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/08/05/Supercolliding-a-PHP-array.html b/2015/08/05/Supercolliding-a-PHP-array.html new file mode 100644 index 0000000..ae39793 --- /dev/null +++ b/2015/08/05/Supercolliding-a-PHP-array.html @@ -0,0 +1,1292 @@ + + + +PHP数组的哈希碰撞 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

PHP数组的哈希碰撞

  + +
+
+ + +

本文翻译自 Nikic 的文章: Supercolliding a PHP array, 我只是业余爱好者, 有句话叫把一本 +好书翻译怀了就是犯罪, 鉴于本人的业余水平, 请以原文为主, 慎重参考.

+ +

你知道插入特别挑选的2^16 = 65536个值到一个普通的 PHP 数组里能花费30秒时间么? 正常情况下, +这一操作只需要0.01秒.

+ + + +

下面的代码可以实践上面的描述:

+ +
$size = pow(2, 16); // 16 is just an example, could also be 15 or 17
+
+$startTime = microtime(true);
+
+$array = array();
+for ($key = 0, $maxKey = ($size - 1) * $size; $key <= $maxKey; $key += $size) {
+    $array[$key] = 0;
+}
+
+$endTime = microtime(true);
+
+echo 'Inserting ', $size, ' evil elements took ', $endTime - $startTime, ' seconds', "\n";
+
+$startTime = microtime(true);
+
+$array = array();
+for ($key = 0, $maxKey = $size - 1; $key <= $maxKey; ++$key) {
+    $array[$key] = 0;
+}
+
+$endTime = microtime(true);
+
+echo 'Inserting ', $size, ' good elements took ', $endTime - $startTime, ' seconds', "\n";
+
+ +

自己试一下, 你可能需要根据自己的机器类型调整$size = pow(2, 16);里面的16. 我自己是从14开始试, +每次增加1. 这里我就不提供在线的例子了, 省得一不小心, 把棒棒哒 +Viper-7 codepad给搞挂了.

+ +

下面时我自己的机器上的程序输出:

+ +
Inserting 65536 evil elements took 32.726480007172 seconds
+Inserting 65536 good elements took 0.014460802078247 seconds
+
+ +

背后的原理?

+ +

PHP 内部使用哈希表来保存数组, 上面的代码创建了一个 100%碰撞(冲突)的哈希表, 也即所有的键都有同样的哈希值.

+ +

这一段写个那些不知道哈希表的人: 在 PHP 里, 当你写下$array[$key]这样的代码时, PHP 会运行一个非常快的哈希函数 +用于产生一个整数, 然后这个整数被用做真正的 C 语言数组的偏移量(这里的真正的数组表示一块内存).

+ +

由于每个哈希函数都会产生碰撞, 所以这个 C 数组并不真正存储我们想存储的值, 它只保存一个保存真实值的链表, 之后 PHP +会一个个遍历链表里的值, 并且比较保存的键直到它找到了$key标识的那个元素.

+ +

通常情况下, 只会有一少部分的碰撞, 于是在大多数情况下, 这个链表只会有一个元素.

+ +

但是上面的脚本产生了一个全部碰撞的哈希表.

+ +

怎么样才会全部碰撞?

+ +

在 PHP 里面实现起来很简单. 如果给定的键是一个整型, 那么哈希函数什么都不干, 哈希值就是这个数字本身, PHP 所作的就是 +把哈希表的掩码和这个值做与运算:hash & tableMask.

+ +

这个哈希表掩码会保证产生的哈希值都落在真正的 C 数组里面, C 数组的边界通常是 2 的整数次幂, 这么做的好处是哈希表在时间 +和空间上性能都比较好. 所以, 如果你保存了 10 个值, 哈希表的真正大小是 16, 保存 33 个就是 64, 保存 63 个还是 64. 哈希表 +掩码是这个最大值减去 1. 所以, 如果大小是 64, 即二进制100 0000, 那么哈希表掩码就是 63(011 1111).

+ +

所以, 哈希表掩码的作用就是, 移除所有超过哈希表大小的位, 这就是我们刚刚玩过的0 & 0111111 = 0, +1000000 & 0111111 = 0, 10000000 & 0111111 = 0 以及 11000000 & 0111111 = 0, 都是 0. 我们只保持这些 +低位不变, 产生的哈希值也会不变(原文: As long as we keep those lower bits the same the result of the hash +will also stay the same.)

+ +

如果我们插入总共 64 个元素, 第一个为 0, 第二个为 64, 第三个时 128, 第四个是 192… 那么这些值就会有相同的哈希值(0) +然后就会被放到同一个链表里面, 上面的代码就干了点这(只不过插入的有点多, 不光是 64 个)…

+ +

为啥这么慢?

+ +

额, 当 PHP 每次插入元素时, 都会一个元素接一个地遍历整个链表. 插入第一个的时候, +只需遍历 0 个元素, 第二个遍历一个, 第三个遍历 2 个… 第 64 个遍历 63 个元素. +有点数学知识的人就会知道0+1+2+3+...+(n-1) = (n-1)*(n-2)/2所以遍历的元素个 +数是平方级(quadratic)的, 64 个元素就是62*63/2 = 1953次遍历. 对于 +2^16 = 65536就是65534*65535/2=2147385345. 现在你看到了,这个数涨的非常快, +相应的执行时间也会加长.

+ +

利用哈希碰撞发动 DOS 攻击

+ +

现在, 你可能想知道上面说的能用来做什么. 对普通人来说, 答案是没啥用. +但是对那些”坏家伙”来说, 他们能很轻易的利用上面的行为来制造 DOS(拒绝服务)攻击. +要知道$_GET$_POST以及$_REQUEST都是普通的数组, 它们都有这里说的问题, +所以, 通过发送精心构造的 POST 请求, 你就可以很轻易的干掉一台服务器.

+ +

PHP 并不是唯一有这种问题的语言, 事实上还有很多语言都有这样的问题, 详情可以查 +看这里: presented at the 28C3 conference.

+ +

但是, 应该看到希望! PHP 已经做了修改(将在 5.3.9 版本发布), 它在 INI 文件添加 +了max_input_vars选项, 默认值为 1000. 这个值决定了能接受的最大的 POST/GET 变量 +数目, 这就意味着只有最多 1000 个碰撞产生, 如果上面的代码只有2^10 = 1024个元素 +那么, 你只用 0.003 秒就能执行完毕, 这比 30 秒的情况好了不止一点.

+ +

还有, 上文我使用了数字键碰撞, 你也可以使用字符串键来产生冲突,这会比数字键更 +费时间

+ +
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/08/07/prepare.html b/2015/08/07/prepare.html new file mode 100644 index 0000000..57e1038 --- /dev/null +++ b/2015/08/07/prepare.html @@ -0,0 +1,1454 @@ + + + +PHP内核, 开始前的准备 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

PHP内核, 开始前的准备

  + +
+
+ + +

代码阅读器

+ +

选用 Vim, 外加 ctags 和 cscope, 爽到爆

+ +

C 语言的几点复习

+ +

宏定义里面的#

+ +

宏定义里面的#表示在字符串前后加上双引号, 比如下面的代码

+ + + +
#include <stdio.h>
+
+#define STR(a) #a
+
+int main()
+{
+    char ab[] = STR(Hello world);
+
+    printf("%s\n", ab);
+}
+
+ +

这段代码输出

+ +
songzg@xxx c$ ./a.out
+Hello world
+
+ +

宏定义里面的##

+ +

宏定义里面的##表示连接字符串, 比如下面的代码

+ +
#include <stdio.h>
+
+#define CONTACT(a, b) STR(a##b)
+#define STR(a) #a
+
+int main()
+{
+    char ab[] = CONTACT(Hel, lo);
+
+    printf("%s\n", ab);
+}
+
+ +

这段代码输出

+ +
songzg@xxx c$ ./a.out
+Hello
+
+ +

line 用法

+ +

有时候, 会见到这种用法

+ +
#line 12 "abc.c"
+
+ +

这是在告诉编译器, 当前代码所在的行是第 15 行, 文件名字叫做 abc.c, 它的作用体现在编译器的编写中,编译器对 C 源码编译过程中会产生一些中间文件,通过这条指令,可以保证文件名是固定的,不会被这些中间文件代替,有利于进行调试分析。

+ +

宏定义中的 do-while 循环

+ +

PHP 的源代码中, 很多宏定义里面用到了do-while循环, 比如这一段

+ +
#define ALLOC_ZVAL(z)                                   \
+    do {                                                \
+        (z) = (zval*)emalloc(sizeof(zval_gc_info));     \
+        GC_ZVAL_INIT(z);                                \
+    } while (0)
+
+ +

我们知道, do-while里面的语句至少会执行一次, 而while条件又是 0 恒为假, 看上去这么写是多此一举, 那么究竟为什么要这么写呢?

+ +

考虑以下代码

+ +
#define TEST(a, b)  a++;b++;
+
+if (expr)
+    TEST(a, b);
+else
+    do_else();
+
+ +

编译器预处理之后, 实际上产生的是下面的代码

+ +
if (expr)
+    a++;b++;;
+else
+    do_else();
+
+ +

这里, if语句块的结构被破坏掉了, 导致编译器会报错. 那么, 我们能不能将宏定义放在{}里面呢? 答案是不, TEST(a, b);语句的最后, 我们会习惯上加上;, 若在宏定义里面用大括号, 这时候还是会破坏语言结构, 一样会导致报错. 所以一般的多表达式宏定义中都采用 do-while(0)的方式.

+ +

空操作

+ +

do-while有时候也被用来执行空操作, 比如这个

+ +
#ifdef SERIALIZE_HEADERS
+#   define VEC_FREE() smart_str_free(&vec_str)
+#else
+#   define VEC_FREE() do {} while (0)
+#endif
+
+ +

SERIALIZE_HEADERS没有定义的时候, 就什么都不做. 有时候空操作这样定义, 这里的空操作和上面的还是不一样,例如很常见的 Debug 日志打印宏

+ +
#ifdef DEBUG
+#   define LOG_MSG printf
+#else
+#   define LOG_MSG(...)
+#endif
+
+ +

PHP 中的全局变量宏

+ +

在 PHP 代码中经常能看到一些类似PG(), EG()之类的函数, 他们都是 PHP 中定义的宏, 这系列宏主要的作用是解决线程安全所写的全局变量包裹宏, +如$PHP_SRC/main/php_globals.h文件中就包含了很多这类的宏. 例如 PG 这个 PHP 的核心全局变量的宏. 如下所示代码为其定义

+ +
#ifdef ZTS
+/* 编译时开启了线程安全则使用线程安全库 */
+#   define PG(v) TSRMG(core_globals_id, php_core_globals *, v)
+extern PHPAPI int core_globals_id;
+#else
+/* 否则这其实就是一个普通的全局变量 */
+#   define PG(v) (core_globals.v)
+extern ZEND_API struct _php_core_globals core_globals;
+#endif
+
+ +

PHP 运行时的一些全局参数,

+ +

这个全局变量为如下的一个结构体, 各字段的意义如字段后的注释

+ +
struct _php_core_globals {
+        zend_bool magic_quotes_gpc; //  是否对输入的GET/POST/Cookie数据使用自动字符串转义。
+        zend_bool magic_quotes_runtime; //是否对运行时从外部资源产生的数据使用自动字符串转义
+        zend_bool magic_quotes_sybase;  //   是否采用Sybase形式的自动字符串转义
+
+        zend_bool safe_mode;    //  是否启用安全模式
+
+        zend_bool allow_call_time_pass_reference;   //是否强迫在函数调用时按引用传递参数
+        zend_bool implicit_flush;   //是否要求PHP输出层在每个输出块之后自动刷新数据
+
+        long output_buffering;  //输出缓冲区大小(字节)
+
+        char *safe_mode_include_dir;    //在安全模式下,该组目录和其子目录下的文件被包含时,将跳过UID/GID检查。
+        zend_bool safe_mode_gid;    //在安全模式下,默认在访问文件时会做UID比较检查
+        zend_bool sql_safe_mode;
+        zend_bool enable_dl;    //是否允许使用dl()函数。dl()函数仅在将PHP作为apache模块安装时才有效。
+
+        char *output_handler;   // 将所有脚本的输出重定向到一个输出处理函数。
+
+        char *unserialize_callback_func;    // 如果解序列化处理器需要实例化一个未定义的类,这里指定的回调函数将以该未定义类的名字作为参数被unserialize()调用,
+        long serialize_precision;   //将浮点型和双精度型数据序列化存储时的精度(有效位数)。
+
+        char *safe_mode_exec_dir;   //在安全模式下,只有该目录下的可执行程序才允许被执行系统程序的函数执行。
+
+        long memory_limit;  //一个脚本所能够申请到的最大内存字节数(可以使用K和M作为单位)。
+        long max_input_time;    // 每个脚本解析输入数据(POST, GET, upload)的最大允许时间(秒)。
+
+        zend_bool track_errors; //是否在变量$php_errormsg中保存最近一个错误或警告消息。
+        zend_bool display_errors;   //是否将错误信息作为输出的一部分显示。
+        zend_bool display_startup_errors;   //是否显示PHP启动时的错误。
+        zend_bool log_errors;   // 是否在日志文件里记录错误,具体在哪里记录取决于error_log指令
+        long      log_errors_max_len;   //设置错误日志中附加的与错误信息相关联的错误源的最大长度。
+        zend_bool ignore_repeated_errors;   //   记录错误日志时是否忽略重复的错误信息。
+        zend_bool ignore_repeated_source;   //是否在忽略重复的错误信息时忽略重复的错误源。
+        zend_bool report_memleaks;  //是否报告内存泄漏。
+        char *error_log;    //将错误日志记录到哪个文件中。
+
+        char *doc_root; //PHP的”根目录”。
+        char *user_dir; //告诉php在使用 /~username 打开脚本时到哪个目录下去找
+        char *include_path; //指定一组目录用于require(), include(), fopen_with_path()函数寻找文件。
+        char *open_basedir; // 将PHP允许操作的所有文件(包括文件自身)都限制在此组目录列表下。
+        char *extension_dir;    //存放扩展库(模块)的目录,也就是PHP用来寻找动态扩展模块的目录。
+
+        char *upload_tmp_dir;   // 文件上传时存放文件的临时目录
+        long upload_max_filesize;   // 允许上传的文件的最大尺寸。
+
+        char *error_append_string;  // 用于错误信息后输出的字符串
+        char *error_prepend_string; //用于错误信息前输出的字符串
+
+        char *auto_prepend_file;    //指定在主文件之前自动解析的文件名。
+        char *auto_append_file; //指定在主文件之后自动解析的文件名。
+
+        arg_separators arg_separator;   //PHP所产生的URL中用来分隔参数的分隔符。
+
+        char *variables_order;  // PHP注册 Environment, GET, POST, Cookie, Server 变量的顺序。
+
+        HashTable rfc1867_protected_variables;  //  RFC1867保护的变量名,在main/rfc1867.c文件中有用到此变量
+
+        short connection_status;    //  连接状态,有三个状态,正常,中断,超时
+        short ignore_user_abort;    //  是否即使在用户中止请求后也坚持完成整个请求。
+
+        unsigned char header_is_being_sent; //  是否头信息正在发送
+
+        zend_llist tick_functions;  //  仅在main目录下的php_ticks.c文件中有用到,此处定义的函数在register_tick_function等函数中有用到。
+
+        zval *http_globals[6];  // 存放GET、POST、SERVER等信息
+
+        zend_bool expose_php;   //  是否展示php的信息
+
+        zend_bool register_globals; //  是否将 E, G, P, C, S 变量注册为全局变量。
+        zend_bool register_long_arrays; //   是否启用旧式的长式数组(HTTP_*_VARS)。
+        zend_bool register_argc_argv;   //  是否声明$argv和$argc全局变量(包含用GET方法的信息)。
+        zend_bool auto_globals_jit; //  是否仅在使用到$_SERVER和$_ENV变量时才创建(而不是在脚本一启动时就自动创建)。
+
+        zend_bool y2k_compliance;   //是否强制打开2000年适应(可能在非Y2K适应的浏览器中导致问题)。
+
+        char *docref_root;  // 如果打开了html_errors指令,PHP将会在出错信息上显示超连接,
+        char *docref_ext;   //指定文件的扩展名(必须含有’.')。
+
+        zend_bool html_errors;  //是否在出错信息中使用HTML标记。
+        zend_bool xmlrpc_errors;
+
+        long xmlrpc_error_number;
+
+        zend_bool activated_auto_globals[8];
+
+        zend_bool modules_activated;    //  是否已经激活模块
+        zend_bool file_uploads; //是否允许HTTP文件上传。
+        zend_bool during_request_startup;   //是否在请求初始化过程中
+        zend_bool allow_url_fopen;  //是否允许打开远程文件
+        zend_bool always_populate_raw_post_data;    //是否总是生成$HTTP_RAW_POST_DATA变量(原始POST数据)。
+        zend_bool report_zend_debug;    //  是否打开zend debug,仅在main/main.c文件中有使用。
+
+        int last_error_type;    //  最后的错误类型
+        char *last_error_message;   //  最后的错误信息
+        char *last_error_file;  //  最后的错误文件
+        int  last_error_lineno; //  最后的错误行
+
+        char *disable_functions;    //该指令接受一个用逗号分隔的函数名列表,以禁用特定的函数。
+        char *disable_classes;  //该指令接受一个用逗号分隔的类名列表,以禁用特定的类。
+        zend_bool allow_url_include;    //是否允许include/require远程文件。
+        zend_bool exit_on_timeout;  //  超时则退出
+#ifdef PHP_WIN32
+        zend_bool com_initialized;
+#endif
+        long max_input_nesting_level;   //最大的嵌套层数
+        zend_bool in_user_include;  //是否在用户包含空间
+
+        char *user_ini_filename;    //  用户的ini文件名
+        long user_ini_cache_ttl;    //  ini缓存过期限制
+
+        char *request_order;    //  优先级比variables_order高,在request变量生成时用到,个人觉得是历史遗留问题
+
+        zend_bool mail_x_header;    //  仅在ext/standard/mail.c文件中使用,
+        char *mail_log;
+
+        zend_bool in_error_log;
+};
+
+ +

上面的字段很大一部分是与php.ini文件中的配置项对应的. 在 PHP 启动并读取php.ini文件时就会对这些字段进行赋值, 而用户空间的ini_get()ini_set()函数操作的一些配置也是对这个全局变量进行操作的.

+ +

在 PHP 代码的其他地方也存在很多类似的宏, 这些宏和PG宏一样, 都是为了将线程安全进行封装,同时通过约定的G命名来表明这是全局的, 一般都是个缩写, 因为这些全局变量在代码的各处都会使用到, 这也算是减少了键盘输入. +我们都应该尽可能的不是么?

+ +

如果你阅读过一些 PHP 扩展话应该也见过类似的宏, 这也算是一种代码规范, 在编写扩展时全局变量最好也使用这种方式命名和包裹, +因为我们不能对用户的 PHP 编译条件做任何假设.

+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/08/09/varying-number-of-arguments.html b/2015/08/09/varying-number-of-arguments.html new file mode 100644 index 0000000..84c85f5 --- /dev/null +++ b/2015/08/09/varying-number-of-arguments.html @@ -0,0 +1,1357 @@ + + + +C语言函数的可变参数 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

C语言函数的可变参数

  + +
+
+ + +

关于可变参数

+ +

大家对printf函数都不陌生, 它的第一个参数是格式化控制字符串, 后面是要打印的值 +打印值可以有任意多个, 这就是所谓的可变参数.

+ +

先看printf的函数原型(man 3 printf)

+ + + +
#include <stdio.h>
+
+int printf(const char *format, ...);
+
+ +

可以看到, 后面的可变数量参数被用省略号代替了. 今天, 我们也来实现一个这样的函数

+ +

来个简单的开开胃

+ +

C 语言在 stdarg.h 头文件提供了一系列宏: va_xxx, 我们可以用它来实现目标. +话不多说, 先来一段简单的

+ +
#include <stdio.h>
+#include <stdarg.h>
+
+void my_func(int argc, ...)
+{
+    int i = 0;
+    int arg;
+    va_list ap;
+
+    va_start(ap, argc);
+    while (i++ < argc) {
+        arg = va_arg(ap, int);
+        printf("ARG %d: %d\n", i, arg);
+    }
+    va_end(ap);
+}
+
+int main()
+{
+    my_func(3, 1, 2, 3);
+
+    return 0;
+}
+
+ +

输出为

+ +
[zhigang@song test]$ gcc -g -Wall varying.c
+[zhigang@song test]$ ./a.out
+ARG 1: 1
+ARG 2: 2
+ARG 3: 3
+
+ +

要点如下

+ +
    +
  • va_list 是 stdarg.h 提供的结构, 用来保存传过来的可变变量
  • +
  • va_start 用于初始化ap
  • +
  • va_arg 用于从ap里面获取传过来的值, 第二个参数是这个位置上参数的类型, +有了这个, va_arg才能准确的帮你找到传进来的值.
  • +
  • 可以想象有个指针指向ap当前值, 用va_arg读取之后, 这个指针指向下一个位置.
  • +
  • va_end用于结束可变参数的获取, 之后ap处于undefined状态, 希望再次获取 +可变参数, 需要重新va_start->va_arg->va_end这样的过程. 特别注意的是, +这个va_end是必需的.
  • +
+ +

man 3 va_start里面有一句

+ +
+

If ap is passed to a function that uses va_arg(ap,type), then the value +of ap is undefined after the return of that function.

+
+ +

大约是说, 要是把ap给了其他使用va_arg的函数, 那么当这个函数返回了之后, +ap就是处于未定义状态的了. 这些函数之间可能有影响.

+ +

来个稍微复杂一点的

+ +

这个例子来自 man pages, 请看代码

+ +
#include <stdio.h>
+#include <stdarg.h>
+
+void foo(char *fmt, ...)
+{
+    va_list ap;
+    int d;
+    char c, *s;
+
+    va_start(ap, fmt);
+    while (*fmt)
+        switch (*fmt++) {
+        case 's':              /* string */
+            s = va_arg(ap, char *);
+            printf("string %s\n", s);
+            break;
+        case 'd':              /* int */
+            d = va_arg(ap, int);
+            printf("int %d\n", d);
+            break;
+        case 'c':              /* char */
+            /* need a cast here since va_arg only
+               takes fully promoted types */
+            c = (char) va_arg(ap, int);
+            printf("char %c\n", c);
+            break;
+        }
+    va_end(ap);
+}
+
+int main()
+{
+    foo("scdss", "Hello", 'A', 105, "all", "fine");
+
+    return 0;
+}
+
+ +

输出如下

+ +
[zhigang@song test]$ gcc varying_man_pages.c -Wall
+[zhigang@song test]$ ./a.out
+string Hello
+char A
+int 105
+string all
+string fine
+
+ +

关于vprintf

+ +

写到这里, 把这个函数和 PHP 的同名函数搞混了,一只以为是输出数组的, 唉, 看了 man +pages 才回过神…这是 C 不是 PHP… 日下狗 :(

+ +

同样在 stdarg.h 下, 还有一个vprintf函数专门用于输出va_list数据的, 原型如下

+ +
#include <stdarg.h>
+
+int vprintf(const char *format, va_list ap);
+
+ +

下面让我们来用一下这个函数

+ +
#include <stdio.h>
+#include <stdarg.h>
+
+void foo(char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    vprintf(fmt, ap);
+    va_end(ap);
+}
+
+int main()
+{
+    foo("%s %c %d %s %s", "Hello", 'A', 105, "all", "fine");
+
+    return 0;
+}
+
+ +

输出如下

+ +
[zhigang@song test]$ gcc -Wall vprintf_test.c
+[zhigang@song test]$ ./a.out
+Hello A 105 all fine
+
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/08/10/gnu-autotools.html b/2015/08/10/gnu-autotools.html new file mode 100644 index 0000000..9a6ba09 --- /dev/null +++ b/2015/08/10/gnu-autotools.html @@ -0,0 +1,1541 @@ + + + +GNU Autotools基础知识 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

GNU Autotools基础知识

  + +
+
+ + +

在 Linux 下写 C 程序, 这套著名的工具包还是要了解一点的. 在网上查了不少东西, 现在挑重点的记录一下.

+ +

Autotools 简介

+ +

autotools 时一套构建系统(build system), 用于把人从写 makefile 的任务里面解放出来. 这套系统有下面五个部分组成

+ + + +
    +
  • autoscan 扫描源代码, 生成 configure.scan, 这是 configure.ac 的模板(老式叫法是: configure.in, 新版本里面已经不这么叫了)
  • +
  • aclocal 通过扫描 configure.ac 生成 aclocal.m4, 这个文件将来被 autoconf 使用, 生成 configure
  • +
  • autoheader 创建 config.h.in 文件, 这个文件是 config.h 的模板, 里面定义了一些系统相关的宏, 供源代码条件编译使用
  • +
  • autoconf 这个上面已经说了, 通过 aclocal.m4 文件生成 configure
  • +
  • automake 这个命令通过 makefile.am 来生成 makefile.in, 其中, makefile.am 需要手动编写, 是 makefile.in 的模板, makefile.in 又是 makefile 的模板
  • +
+ +

流程图解

+ +

IBM的技术文档里找到了这么一张图, 帮助理解

+ +

+ +

真刀实枪的来一发

+ +

先写一个 main.c, 然后我们试着用 autotools 来编译它

+ +
#include <stdio.h>
+
+int main()
+{
+    printf("Hello world\n");
+
+    return 0;
+}
+
+ +

下面, 开始编译

+ +
    +
  1. +

    执行autoscan命令(报的这个错, 不知道是什么原因, 不影响使用)

    + +
    [zhigang@song hello]$ autoscan
    +Unescaped left brace in regex is deprecated, passed through in regex; marked by <-- HERE in m/\${ <-- HERE [^\}]*}/ at /usr/bin/autoscan line 361.
    +[zhigang@song hello]$ ls
    +autoscan.log  configure.scan  main.c
    +
    +
  2. +
  3. +

    重命名 configure.scan 为 autoscan.ac, 然后编辑它(这里, 我把 autoscan.log 删掉了, 没啥用)

    + +
    #                                               -*- Autoconf -*-
    +# Process this file with autoconf to produce a configure script.
    +
    +AC_PREREQ([2.69])
    +AC_INIT(hello, 0.1, bug@hello.hello)
    +AC_CONFIG_SRCDIR([main.c])
    +AC_CONFIG_HEADERS([config.h])
    +
    +# Checks for programs.
    +AC_PROG_CC
    +
    +# Checks for libraries.
    +
    +# Checks for header files.
    +
    +# Checks for typedefs, structures, and compiler characteristics.
    +
    +# Checks for library functions.
    +
    +AM_INIT_AUTOMAKE
    +AC_OUTPUT(Makefile)
    +
    + +

    由于我们这个源代码太简单了, 基本上不用改东西(回头弄一个复杂一点的, 今天先看简单的). 改动有

    + +
      +
    • AC_INIT(hello, 0.1, bug@hello.hello), 这里填上软件名称, 版本 +以及 bug 提交的 email
    • +
    • 添加AM_INIT_AUTOMAKE, 有这一句才会产生 aclocal.m4
    • +
    • AC_OUTPUT(makefile) 表示在当前文件夹里面生成一个 Makefile 文件
    • +
    +
  4. +
+ +

补充

+ +
    +
  • +

    AC_ARG_WITH

    + +

    原型为

    + +

    AC_ARG_WITH (package, help-string, [action-if-given], [action-if-not-given])

    + +

    就是通常见到的--with-foo=bar选项, action-if-givenaction-if-not-gived +分别指定了给出这个选项和不给出这个选项的动作, 比如 03 年(时间有点早了), +memcached 的 configure.ac 里面有如下一段:

    + +
    AC_ARG_WITH(libevent,
    +            AC_HELP_STRING([--with-libevent=DIRECTORY],[base directory
    +            for libevent]))
    +if test ${with_libevent+set} = set && test $with_libevent != no; then
    +    CFLAGS="$CFLAGS -I$with_libevent/include"
    +    LDFLAGS="$LDFLAGS -L$with_libevent/lib"
    +fi
    +
    +
  • +
  • +

    AC_CHECK_HEADER

    + +

    原型

    + +

    AC_CHECK_HEADER(malloc.h, AC_DEFINE(HAVE_MALLOC_H,,[do we have malloc.h?]))

    +
  • +
  • +

    AC_ARG_ENABLE

    + +

    ``

    + +

    AC_CACHE_CHECK (message, cache-id, commands-to-set-it)

    + +

    AC_TRY_LINK (includes, function-body, [action-if-true], [action-if-false])

    +
  • +
+ +
    +
  1. +

    执行aclocal, 生成 aclocal.m4

    + +
    [zhigang@song hello]$ aclocal
    +[zhigang@song hello]$ ls
    +aclocal.m4  autom4te.cache  configure.ac  main.c
    +
    +
  2. +
  3. +

    执行autoheader, 生成 config.h.in

    + +
    [zhigang@song hello]$ autoheader
    +[zhigang@song hello]$ ls
    +aclocal.m4  autom4te.cache  config.h.in  configure.ac  main.c
    +
    +
  4. +
  5. +

    执行autoconf来生成 configure 脚本

    + +
    [zhigang@song hello]$ autoconf
    +[zhigang@song hello]$ ls
    +aclocal.m4  autom4te.cache  config.h.in  configure  configure.ac  main.c  Makefile.am
    +
    +
  6. +
  7. +

    编写 makefile.am 文件, 然后执行automake生成 Makefile

    + +

    下面是 makefile.am 文件内容

    + +
    bin_PROGRAMS=hello
    +hello_SOURCES=main.c
    +
    + +

    这里有需要注意的地方, 按照 GNU 的要求, 源代码下面, 还应该有一下几个文件

    + +
      +
    • ChangeLog
    • +
    • AUTHORS
    • +
    • NEWS
    • +
    • README
    • +
    • COPYING (可以使用--add-missing生成)
    • +
    • 其他 (可以使用--add-missing生成)
    • +
    + +

    直接执行, 必然会报错, 应该添加这几个文件后, 再加上--add-missing执行

    + +
    [zhigang@song hello]$ touch NEWS ChangeLog AUTHORS README
    +[zhigang@song hello]$ automake --add-missing
    +Unescaped left brace in regex is deprecated, passed through in regex; marked by <-- HERE in m/\${ <-- HERE ([^ \t=:+{}]+)}/ at /usr/bin/automake line 3936.
    +configure.ac:10: installing './compile'
    +configure.ac:20: installing './install-sh'
    +configure.ac:20: installing './missing'
    +Makefile.am: installing './INSTALL'
    +Makefile.am: installing './COPYING' using GNU General Public License v3 file
    +Makefile.am:     Consider adding the COPYING file to the version control system
    +Makefile.am:     for your code, to avoid questions about which license your project uses
    +[zhigang@song hello]$ ls
    +aclocal.m4  autom4te.cache  compile	 configure     COPYING	install-sh  Makefile.am  missing  README
    +AUTHORS     ChangeLog	    config.h.in  configure.ac  INSTALL	main.c	    Makefile.in  NEWS
    +
    + +

    可以看到, automake为我们添加了好多额外的文件

    +
  8. +
  9. 现在, 可以执行configure来生成 makefile 啦
  10. +
  11. 执行生成的 makefile, 就会把你的程序编译出来啦.
  12. +
+ +

configure.ac 文件注解

+ +
    +
  1. configure.ac 文件很有特色, 一定要以AC_INIT开头, 以AC_OUTPUT结束.
  2. +
  3. AC_PROG_CC指定需要检查编译器
  4. +
  5. AC_PROG_RANLIB要生成静态库, 要检查此项
  6. +
  7. AC_PROG_LIBTOOL要生成动态库, 要检查此项
  8. +
  9. AC_CHECK_LIB(lib, function)查看lib库里面是不是有function函数
  10. +
  11. AM_INIT_AUTOMAKE, 这个在上面强调过了, 没有这个就无法生成aclocal.m4
  12. +
+ +

makefile.am 文件注解

+ +
    +
  1. 可执行文件 +
      +
    • bin_PROGRAMS=foo
    • +
    • foo_SOURCES=foo.c
    • +
    • foo_LDADD=
    • +
    • foo_LDFLAGS=
    • +
    • foo_DEPENDENCIES=
    • +
    +
  2. +
  3. 静态库 +
      +
    • lib_LIBRARIES=libfoo.a
    • +
    • foo_a_SOURCES=foo.c
    • +
    • foo_a_LDADD=
    • +
    • foo_a_LDFLAGS=
    • +
    • foo_a_DEPENDENCIES=
    • +
    +
  4. +
  5. 头文件, include_HEADERS=foo.h
  6. +
  7. 数据文件, data_DATA=data1 data2
  8. +
  9. Makefile.am 里面提供的全局变量有 +
      +
    • INCLUDES 链接时需要提供的头文件
    • +
    • LDADD 链接时需要的库娃额键
    • +
    • LDFLAGS 链接时提供的库文件选项标志
    • +
    • EXTRA_DIST 打包
    • +
    • SUBDIRS 处理当前目录之前, 先递归处理这些子目录
    • +
    +
  10. +
  11. 对于静态库或者可执行文件来说, 若只希望编译, 而不要安装, 可以用noinst前缀替换相应的bin/lib
  12. +
  13. Makefile.am 里面的路径变量 +
      +
    • $(top_srcdir)定义了工程的顶层目录, 用于引用源程序
    • +
    • $(top_builddir)定义生成目标文件最上层目录, 用于引用*.o等编译出来的目标文件
    • +
    +
  14. +
  15. automake 提供了一些默认的安装路径(/usr/local), 可以在执行configure时使用--prefix=/path/to/install来覆盖. 其他预定义的变量还有 +bindir=$(prefix)/bin, libdir=$(prefix)/lib, datadir=$(prefix)/share, sysconfdir=$(prefix)/etc
  16. +
  17. 每个 source 子目录都有一个 makefile.am
  18. +
+ +
---
+title:  "GNU Autotools基础知识"
+date:   2015-08-10 12:14:04
+tags: c
+---
+
+* `AC_ARG_WITH`
+
+    原型为
+
+    `AC_ARG_WITH (package, help-string, [action-if-given], [action-if-not-given])`
+
+    就是通常见到的`--with-foo=bar`选项, `action-if-given``action-if-not-gived`
+    分别指定了给出这个选项和不给出这个选项的动作, 比如03年(时间有点早了),
+    memcached的configure.ac里面有如下一段:
+
+    ```bash
+    AC_ARG_WITH(libevent,
+                AC_HELP_STRING([--with-libevent=DIRECTORY],[base directory
+                for libevent]))
+    if test ${with_libevent+set} = set && test $with_libevent != no; then
+        CFLAGS="$CFLAGS -I$with_libevent/include"
+        LDFLAGS="$LDFLAGS -L$with_libevent/lib"
+    fi
+    ```
+
+* `AC_CHECK_HEADER`
+
+    原型
+
+    `AC_CHECK_HEADER(malloc.h, AC_DEFINE(HAVE_MALLOC_H,,[do we have malloc.h?]))`
+
+
+* `AC_ARG_ENABLE`
+
+    ``
+
+
+
+    AC_CACHE_CHECK (message, cache-id, commands-to-set-it)
+
+    AC_TRY_LINK (includes, function-body, [action-if-true], [action-if-false])
+
+
+AC_PREREQ(2.52)
+AC_INIT(memcached, 1.1.9-snapshot, brad@danga.com)
+AC_CONFIG_SRCDIR(memcached.c)
+
+AC_CONFIG_HEADER(config.h)
+
+AC_PROG_CC
+AC_PROG_INSTALL
+
+AC_ARG_WITH(libevent,
+	AC_HELP_STRING([--with-libevent=DIRECTORY],[base directory for libevent]))
+if test ${with_libevent+set} = set && test $with_libevent != no; then
+	CFLAGS="$CFLAGS -I$with_libevent/include"
+	LDFLAGS="$LDFLAGS -L$with_libevent/lib"
+fi
+
+LIBEVENT_URL=http://www.monkey.org/~provos/libevent/
+AC_CHECK_LIB(event, event_set, ,
+	[AC_MSG_ERROR(libevent is required.  You can get it from $LIBEVENT_URL)])
+
+AC_CHECK_HEADER(malloc.h, AC_DEFINE(HAVE_MALLOC_H,,[do we have malloc.h?]))
+
+dnl From licq: Copyright (c) 2000 Dirk Mueller
+dnl Check if the type socklen_t is defined anywhere
+AC_DEFUN(AC_C_SOCKLEN_T,
+[AC_CACHE_CHECK(for socklen_t, ac_cv_c_socklen_t,
+[
+  AC_TRY_COMPILE([
+    #include <sys/types.h>
+    #include <sys/socket.h>
+  ],[
+    socklen_t foo;
+  ],[
+    ac_cv_c_socklen_t=yes
+  ],[
+    ac_cv_c_socklen_t=no
+  ])
+])
+if test $ac_cv_c_socklen_t = no; then
+  AC_DEFINE(socklen_t, int, [define to int if socklen_t not available])
+fi
+])
+
+AC_C_SOCKLEN_T
+
+dnl Default to building a static executable.
+AC_ARG_ENABLE(static,
+	AC_HELP_STRING([--disable-static],[build a dynamically linked executable]),
+	, enable_static=yes)
+AC_MSG_CHECKING(whether to build a static executable)
+if test "$enable_static" = "no"; then
+	STATIC=
+	AC_MSG_RESULT(no)
+else
+	CFLAGS="$CFLAGS -static"
+	AC_MSG_RESULT(yes)
+fi
+
+AC_CONFIG_FILES(Makefile)
+AM_INIT_AUTOMAKE
+AC_OUTPUT
+
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/08/11/python-file-read.html b/2015/08/11/python-file-read.html new file mode 100644 index 0000000..ac8ba7c --- /dev/null +++ b/2015/08/11/python-file-read.html @@ -0,0 +1,1268 @@ + + + +Python文本处理 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Python文本处理

  + +
+
+ + +

整理自网上, 东拼西凑加上自己的理解组合的.

+ +

关于文本处理

+ +

我们谈到”文本处理”时, 我们通常是指处理的内容. Python 将文本文件的内容读入可以操作的字符串变量非常容易. +文件对象提供了三个”读”方法

+ + + +
    +
  1. read()
  2. +
  3. readline()
  4. +
  5. readlines()
  6. +
+ +

每种方法可以接受一个变量以限制每次读取的数据量, 但它们通常不使用变量. read()每次读取整个文件, 它通常用于将文件内容放到一个字符串变量中. +然而read()生成文件内容最直接的字符串表示, 但对于连续的面向行的处理, 它却是不必要的, 并且如果文件大于可用内存, 则不可能实现这种处理. +.readline() 和 .readlines() 非常相似。它们都在类似于以下的结构中使用:

+ +

下面是一段 Python readlines()的示例

+ +
#!/bin/env python2
+#-*- coding: utf-8 -*-
+
+fh = open('/tmp/abc', 'r')
+for line in fh.readlines():
+    print line,
+
+print "=" * 30
+
+fh.seek(0, 0)
+
+content = fh.readline()
+while content:
+    print content,
+    content = fh.readline()
+
+print "=" * 30
+
+fh.seek(0, 0)
+
+content = fh.read()
+print content
+
+ +

执行结果如下

+ +
[zhigang@song hello]$ echo -e "aaa\nbbb\nccc" > /tmp/abc
+[zhigang@song hello]$ cat /tmp/abc
+aaa
+bbb
+ccc
+[zhigang@song hello]$ python test.py
+aaa
+bbb
+ccc
+==============================
+aaa
+bbb
+ccc
+==============================
+aaa
+bbb
+ccc
+
+ +

总结

+ +
    +
  1. read一次性读出所有东西(你要是加参数叫它少读点另说)
  2. +
  3. readlines是一次读取整个文件到一个能用于 for…in 结构的列表里面去
  4. +
  5. readline才是实实在在的一次读一行
  6. +
  7. 这三个函数都可以接受一个size参数, 用来指定最多读多少. 超过了就罢工, 对readlines来说, +这是个约数(返回的是完整的行). 对readreadline来说, 实打实的, 你要多少就读多少.
  8. +
+ +

附, 关于写的问题

+ +
    +
  1. writeline()是输出后换行, 下次写会在下一行写.
  2. +
  3. write()是输出后光标在行末不会换行,下次写会接着这行写
  4. +
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/08/12/sql-join.html b/2015/08/12/sql-join.html new file mode 100644 index 0000000..a39658e --- /dev/null +++ b/2015/08/12/sql-join.html @@ -0,0 +1,1252 @@ + + + +SQL语句里面的join - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

SQL语句里面的join

  + +
+
+ + +

对于 SQL 中inner join, outer joincross join的区别很多人不知道, 我也是别人问起, 才查找资料看了下, 跟自己之前的认识差不多. +如果你使用 join 连表, 默认的情况下是inner join. 另外, 开发中使用的left joinright join属于outer join, outer join还包括full join. +下面我通过图标让大家认识它们的区别. 现有两张表, Table A 是左边的表, Table B 是右边的表. 其各有四条记录, 其中有两条记录 name 是相同的:

+ + + +

+ +
    +
  1. +

    INNER JOIN产生的结果是 AB 的交集

    + +
    SELECT * FROM TableA a INNER JOIN TableB b ON a.name = b.name
    +
    + +

    结果和示意图如下

    + +

    +

    +
  2. +
  3. +

    LEFT [OUTER] JOIN产生表 A 的完全集, 而 B 表中匹配的则有值, 没有匹配的则以null值取代.

    + +
    SELECT * FROM TableA a LEFT OUTER JOIN TableB b ON a.name = b.name
    +
    + +

    LEFT [OUTER] JOIN产生表 A 的完全集, 而 B 表中匹配的则有值, 不匹配的以null代替

    + +

    +

    +
  4. +
  5. +

    RIGHT [OUTER] JOIN 产生表 B 的完全集, 而 A 表中匹配的则有值, 没有匹配的则以null值取代.

    + +
    SELECT * FROM TableA a RIGHT OUTER JOIN TableB b ON a.name = b.name
    +
    + +

    图标如 left join 类似

    +
  6. +
  7. +

    FULL [OUTER] JOIN产生 A 和 B 的并集

    + +
    SELECT * FROM TableA a FULL OUTER JOIN TableB b ON a.name = b.name
    +
    + +

    对于没有匹配的记录, 则会以null做为值.

    + +

    你可以通过is NULL将没有匹配的值找出来:

    + +
    SELECT * FROM TableA a FULL OUTER JOIN TableB b ON a.name = b.name WHERE a.id IS null OR b.id IS null;
    +
    + +

    FULL [OUTER] JOIN产生 A 和 B 的并集

    + +

    +

    +
  8. +
  9. +

    CROSS JOIN把表 A 和表 B 的数据进行一个N*M的组合, 即笛卡尔积. 如本例会产生4*4=16条记录, 在开发过程中我们肯定是要过滤数据, 所以这种很少用.

    + +
    SELECT * FROM TableA CROSS JOIN TableB
    +
    +
  10. +
+ +

现在, 相信大家对inner join, outer joincross join的区别已经一目了然了.

+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/08/18/YII2-Behavior.html b/2015/08/18/YII2-Behavior.html new file mode 100644 index 0000000..311c98b --- /dev/null +++ b/2015/08/18/YII2-Behavior.html @@ -0,0 +1,1212 @@ + + + +YII2的Behavior总结 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

YII2的Behavior总结

  + +
+
+ + +

Yii2 官方仓库, Qiang Xue 第一次提交, commit: fc7c1f…. 虽然没看过 Yii1 的代码, 但是还是觉得是扒过来的, +因为正式发布的版本和这次提交大不一样. 这里扣出来这个原始版本, 体会一下其中的思想.

+ + + +

基本思想

+ +

感觉 Behavior 的想法很简单, 创建了一个 Behavior 类, 有两个私有成员变量: $_owner$_enabled. +前者记录这个 Behavior 的拥有者(这个 Behavior 是在那个 component 来的), 后者记录这个 Behavior 是不是启用.

+ +

之后是一个很重要的成员函数events, 但是这个版本里只有一句话return array();. 此函数声明了一个数组, +用来盛放事件和事件处理函数. 事件在 component 里面定义, 而处理函数是通过 behavior 类来完成. +当 behavior 绑定到 component 时, 处理函数会绑定到特定的事件. 当 behavior 从 component 解绑的时候, +事件和处理函数也解绑. 这个函数返回一个关联数组, 数组的键是事件, 值是处理函数.

+ +

下一个函数是attach, 它接受一个 component 参数, 并把自己的events遍历一遍, 按照键/值对应事件/处理函数的关系, +调用 component 自身的 attachEventHandler, 把事件和响应机制安装到 component 上. 关于 component 我会再写一篇文章专门解释.

+ +

与之对应的是detach, 它调用 component 的 detachEventHandler 把响应函数和事件解绑, 并把$_owner注销.

+ +

再一个比较重要的成员变量是$_enabled, 它标示这个 Behavior 是不是启用. 当这个值状态改变为启用/关闭时, 会把所用的事件和响应绑定/解绑

+ +

暂时就这么多, 这次提交是最原始的版本, 到 YII2 发布差了差不多 2 年, 之后这个文件的注解, 再慢慢地添加.

+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/08/18/YII2-component.html b/2015/08/18/YII2-component.html new file mode 100644 index 0000000..0f11849 --- /dev/null +++ b/2015/08/18/YII2-component.html @@ -0,0 +1,1280 @@ + + + +YII2的Component总结 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

YII2的Component总结

  + +
+
+ + +

Yii2 官方仓库, Qiang Xue 第一次提交, commit: fc7c1f…. 虽然没看过 Yii1 的代码, +估计是扒过来的, 因为正式发布的版本和这次提交大不一样. 这里扣出来这个原始版本, +体会一下其中的思想.

+ + + +

基本思想

+ +

component 还是比较重要的, 很多东西都继承了这个类.

+ +

获取与设定操作

+ +

这里的实现也很简单, 刚开始是一系列魔术方法, __get以及__set方法用于 +获取/设定成员变量, 当请求变量有getXXX/setXXX方法时, 就会非常简单地执行这个 +方法, 得到相应的值.

+ +

对于其他, 来看看怎么操作的:

+ +
    +
  1. +

    当请求的是一个事件的时候, component 自身有一个$_e私有成员变量, +里面记录有一堆事件(new yii\collections\Vector), 此时会返回这个记录.

    +
  2. +
  3. +

    这里还有一个私有成员变量$_m, 应该是存放 Behavior 对象的, 以上两种都不是, +就走到这里了. 如果存放的有这么个事件就返回, 没有就遍历存放的 Behavior 对象, +找到关联的事件之后, 就返回. 否则, 程序会执行到函数末尾, 会触发报错.

    +
  4. +
  5. +

    对于设定(set), 处理方法于 get 一样, 多了设定操作.

    +
  6. +
+ +

__call魔术方法

+ +

这个魔术方法实现了事件, 当我们执行一个不是方法的函数时, 就会触发这个魔术方法, +YII 就会遍历绑定到这个 component 上的 behavior, 找到相应的事件, 执行之.

+ +

若在事件里面没有找到这个方法, 就会尝试查看该 component 的这个属性是不是匿名函数(Closure). +若是, 就会尝试调用它.

+ +

attachBehaviordetachBehavior

+ +

attachBehavior 用于绑定一个 behavior 到 component. 该方法接收两个参数, behavior 的$name和 +behavior 本身$behavior. 程序首先检查$behavior参数, 确保它实现了IBehavior. 若没有实现, +会认为它是创建新 behavior 的配置数据, 这个数据会被传给 YiiBase 的createComponent方法来创建一 +个 behavior 替代这个传入的. 然后, 调用 behavior 的attach方法来初始化

+ +

至于detachBehavior就比较简单了, 调用这个 behavior 的detach方法, 然后unset这个 behavior, +最后返回这个 behavior 即可. 当然了, 这里的 behavior 是继承了 component 的, unset会触发 component +的__unset魔术方法, 这是需要注意的地方.

+ +

__unset魔术方法相关资料

+ +

__unset方法, 看这个方法之前呢,我们也先来看一下unset()这个函数,unset(). 这个函数的作用 +是删除指定的变量且传回 true, 参数为要删除的变量. 那么如果在一个对象外部去删除对象内部的成员属性 +用unset()函数可不可以呢? 这里分两种情况, 如果一个对象里面的成员属性是公有的, 就可以使用这个 +函数在对象外面删除对象的公有属性, 如果对象的成员属性是私有的, 我使用这个函数就没有权限去删除. +但如果你在一个对象里面加上__unset()这个方法, 就可以在对象的外部去删除对象的私有成员属性了. +在对象里面加上了__unset()这个方法之后, 在对象外部使用unset()函数删除对象内部的私有成员属性时, +自动调用__unset()函数来帮我们删除对象内部的私有成员属性, 这个方法也可以在类的内部定义成私有的. +在对象里面加上下面的代码就可以了:

+ +
private function __unset($nm){
+    echo "当在类外部使用unset()函数来删除私有成员时自动调用的<br>";
+    unset($this->$nm);
+}
+
+ +

__unset魔术方法里面, 针对 behavior 注销, 做了两个假设:

+ +
    +
  1. 就是一个普通的 behavior, 调用detachBehavior注销之即可.
  2. +
  3. 是某个 behavior 的 property, 那么需要把这个 property 设为null, 其他保持不变.
  4. +
+ +

attachEventHandler, detachEventHandler以及raiseEvent

+ +

前两个方法用于事件和处理函数绑定与解绑. 不管是绑定还是解绑,都用到了event类 +自身的处理方法, 即addremove方法.

+ +

raiseEvent负责生成事件, 并执行处理函数. 正常情况下, 这个方法会执行所有的事 +件处理函数, 但是若这个事件的$handled成员变量被设为true, 则之后的处理函数 +都将忽略.

+ +

evaluateExpression方法

+ +

这个方法用于在当前 component 环境中执行一段 PHP 代码或者一个可调用变量

+ +

暂时就这么多, 这次提交是最原始的版本, 到 YII2 发布差了差不多 2 年, 之后这个文件的注解, +再慢慢地添加.

+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/08/21/PHP-object-pass-by-referance.html b/2015/08/21/PHP-object-pass-by-referance.html new file mode 100644 index 0000000..7bd3c8f --- /dev/null +++ b/2015/08/21/PHP-object-pass-by-referance.html @@ -0,0 +1,1197 @@ + + + +PHP对象传递, 赋值 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

PHP对象传递, 赋值

  + +
+
+ + +

按值传递和引用传递

+ +

对象赋值是引用赋值, 这点应该特别注意

+ +
$a = $obj;
+$obj->name = "bar";
+$a->name = "foo";
+echo $obj->name; // output is foo
+
+ + +
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/08/21/Yii2-YiiBase.html b/2015/08/21/Yii2-YiiBase.html new file mode 100644 index 0000000..3b71df2 --- /dev/null +++ b/2015/08/21/Yii2-YiiBase.html @@ -0,0 +1,1271 @@ + + + +YiiBase小记 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

YiiBase小记

  + +
+
+ + +

总说

+ +

YiiBase 是 Yii 的基础包装, 可以理解成司令官的角色. 它用于自动加载, 创建别名, +把别名转换为地址, 翻译以及其他一系列操作.

+ + + +

createApplication

+ +

这是第一个比较要紧的方法, 根据传入的目标应用名称和配置, 会然会相应的应用对象.

+ +

createComponent

+ +

这个类用给定的配置来创建 component. 配置可以是字符串或者是数组. 如果是字符串, +它会被当做是别名或者类名称. 若配置是数组形式, 则此数组的class元素将会被当做 +对象类型(理解成类名), 之后的键值对会被当做新创建的对象的属性(property).

+ +

若向这个方法传递了其他参数, 那么这些参数会被传递给新创建对象的构造函数(这里涉 +及到了 ReflactionClass, 之前一直不知道 Reflaction 用在哪里, 这也算是一个实际应用吧).

+ +

import

+ +

此方法用于引入一个类或者目录.

+ +

引入一个类的操作和include此类的类文件一样, 主要的不同点在于引入一个类比引入一 +个类文件更加轻量, 因为引入一个类会在用到这个类的时候再去引入这个类文件.

+ +

引入一个目录和向 PHP 的include path里面添加一个目录是一样的, 当有多个目录被引入 +时, 后引入的目录会有更高的优先级, 也即: 它们被加在 PHPinclude path的前面.

+ +

可以使用别名来引入类或者目录, 比如application.components.GoogleMap用于引入 +GoogleMap类. 而application.components.*用于引入components目录.

+ +

同一个别名可以被多次import, 但只有第一次是有效的, 而且, 引入目录不会引入目 +录的子目录.

+ +

从 1.1.5 版本(要求 PHP >= 5.3)开始, import也可以接受名称空间参数, 工作方法和引 +入别名差不多, 只是点号分隔符被换为了反斜线. 此时, 名称空间的写法就需要遵守一 +定的规则了(用于方便的转换到目录). 规则要求, 当名称空间里面的反斜线被替换为点 +号时, 能组成一个有效的路径别名(path alias).

+ +

getPathOfAlias, setPathOfAlias

+ +

下面来看setPathOfAlias, 此方法用于设定一个别名, 当传入的path为空, 就会删 +除这个别名. 这个类既不检查路径是否存在, 也不检查路径是不是规范, 一切靠自觉, +你给啥, 就是啥

+ +

要紧的是getPathOfAlias, 它用于把一个别名转换为一个文件地址. 这个方法不检查 +生成的目标文件地址是不是存在, 它仅仅检查别名里面的root alias的合法性.

+ +

注意: findModel函数没有研究过, 这里调用到了.

+ +

autoload 和 registerAutoloader

+ +

autoload被 PHP 的魔术方法__autoload调用, 这里实现了即用即加载. +registerAutoloader用于注册新的 autoloader, 它能确保$app的 +autoload有最高的优先级

+ +

t 方法

+ +

这个方法是用于将一个消息翻译为特定的语种. 自 1.0.2 版本开始, 该方法开始支持 +choice format, 也即返回消息将会根据这个参数返回. 种特性主要是为了解决某些 +语言中, 一个消息可以有多种形式的说法(这里翻译的不好, 不知道是不是这个意思).

+ +

$category参数标示了消息类别, 这里应该只使用字母, 并且要注意 ‘yii’ 关键字 +保留用于 Yii 框架核心代码使用了. 在PhpMessageSource类里面能找到详细的使用说明

+ +

$params 参数指定strtr函数使用的参数, 从 1.0.2 版本开始, t方法支持在这个参 +数第一个元素作为数字(没有关联的键)指定choice format, 到 1.1.6 版本, 开始支持向 +ChoiceFormat::format传参时无需使用数组包装.

+ +

$source参数指定应用程序那个消息源, 默认是null, 意味着对 yii 的category使用 +coreMessages 源, 对其他category使用 messages 消息源.

+ +

$language指定目标语言, 当不指定时, 调用Application::getLanguage确定翻译为 +何种语言, 此参数自 1.0.3 开始可用

+ +

TODO: ChoiceFormat::format, 具体的定义当前版本里面没有定义, 以后再看

+ +

其他

+ +

还有一些琐碎的方法, 像日志, debug 等, 都比较简单, 不介绍了

+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/08/22/PHP-array.html b/2015/08/22/PHP-array.html new file mode 100644 index 0000000..2c280b1 --- /dev/null +++ b/2015/08/22/PHP-array.html @@ -0,0 +1,1195 @@ + + + +PHP数组总结 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

PHP数组总结

  + +
+
+ + +

在数组里面插入值

+ +
array array_splice(array &$input, int $offset [, int $length = 0 [, mixed $replacement ]])`
+
+ +

这个函数本来是用于在数组里面删除并替换一部分内容的, 当我们删掉的内容为空, +同时又有$replacement参数时, 就产生了插入的效果.

+ + +
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/08/25/Yii-Model.html b/2015/08/25/Yii-Model.html new file mode 100644 index 0000000..557dc91 --- /dev/null +++ b/2015/08/25/Yii-Model.html @@ -0,0 +1,1192 @@ + + + +Yii model总结 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Yii model总结

  + +
+
+ + +

Model 简介

+ + +
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/08/25/Yii-Vector-Dictionary.html b/2015/08/25/Yii-Vector-Dictionary.html new file mode 100644 index 0000000..bab016c --- /dev/null +++ b/2015/08/25/Yii-Vector-Dictionary.html @@ -0,0 +1,1192 @@ + + + +Yii的Vector和Dictionary - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Yii的Vector和Dictionary

  + +
+
+ + +

简介

+ + +
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/09/10/C-io-fcntl.html b/2015/09/10/C-io-fcntl.html new file mode 100644 index 0000000..9d9d78b --- /dev/null +++ b/2015/09/10/C-io-fcntl.html @@ -0,0 +1,1192 @@ + + + +操作文件描述符的属性 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

操作文件描述符的属性

  + +
+
+ + +

##

+ + +
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/09/10/Javascript-default-arguments.html b/2015/09/10/Javascript-default-arguments.html new file mode 100644 index 0000000..10b8192 --- /dev/null +++ b/2015/09/10/Javascript-default-arguments.html @@ -0,0 +1,1212 @@ + + + +Javascript函数默认参数 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Javascript函数默认参数

  + +
+
+ + +

php 有个很方便的用法是在定义函数时可以直接给参数设默认值,如:

+ +
function simue ($a=1,$b=2){
+    return $a+$b;
+}
+echo simue(); //输出3
+echo simue(10); //输出12
+echo simue(10,20); //输出30
+
+ + + +

但 js 却不能这么定义,如果写function simue(a=1,b=2){}会提示缺少对象。

+ +

js 函数中有个储存参数的数组 arguments +,所有函数获得的参数会被编译器挨个保存到这个数组中。于是我们的 js 版支持参数默认值的函数可以通过另外一种变通的方法实现,修改上例:

+ +
function simue() {
+  var a = arguments[0] ? arguments[0] : 1;
+  var b = arguments[1] ? arguments[1] : 2;
+  return a + b;
+}
+alert(simue()); //输出3
+alert(simue(10)); //输出12
+alert(simue(10, 20)); //输出30
+
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/09/10/Javascript-variables-type.html b/2015/09/10/Javascript-variables-type.html new file mode 100644 index 0000000..bfab3a6 --- /dev/null +++ b/2015/09/10/Javascript-variables-type.html @@ -0,0 +1,1264 @@ + + + +Javascript 如何判断一个变量的类型 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Javascript 如何判断一个变量的类型

  + +
+
+ + +

js 判断变量类型 有 2 种方法

+ +
    +
  1. 使用typeof
  2. +
  3. 使用Variables.Constructor
  4. +
+ +

Example:

+ + + +
<script type="text/javascript">
+function fun(msg)
+{
+
+    //使用typeof判断
+    if(typeof msg=="string")
+    {
+        alert("使用typeof判断:"+msg);
+    }
+    //使用constructor判断
+    if(msg.constructor==String)
+    {
+        alert("使用constructor判断"+msg);
+    }
+}
+fun("aa");
+</script>
+
+ +

下面是一个详细的列表:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Variabletypeof VariableVariable.constructor
{ an:“object” }objectObject
[ “an”,“array” ]objectArray
function() {}functionFunction
“a string”stringString
55numberNumber
TruebooleanBoolean
new User()objectUser
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/09/11/libevent-tiny-introduction.html b/2015/09/11/libevent-tiny-introduction.html new file mode 100644 index 0000000..b22381b --- /dev/null +++ b/2015/09/11/libevent-tiny-introduction.html @@ -0,0 +1,2254 @@ + + + +C libevent异步编程简介 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

C libevent异步编程简介

  + +
+
+ + +

A tiny introduction to asynchronous IO

+ + + +
+

These documents are Copyright (c) 2009-2012 by Nick Mathewson, and are made +available under the Creative Commons Attribution-Noncommercial-Share Alike +license, version 3.0. Future versions may be made available under a less +restrictive license.

+ +

Additionally, the source code examples in these documents are also licensed +under the so-called “3-Clause” or “Modified” BSD license. See the +license_bsd file distributed with these documents for the full terms.

+ +

For the latest version of this document, see +TOC

+ +

To get the source for the latest version of this document, install git and +run “git clone git://github.com/nmathewson/libevent-book.git”

+
+ +

Most beginning programmers start with blocking IO calls. An IO call is +synchronous if, when you call it, it does not return until the operation is +completed, or until enough time has passed that your network stack gives up. +When you call “connect()” on a TCP connection, for example, your operating +system queues a SYN packet to the host on the other side of the TCP connection. +It does not return control back to your application until either it has +received a SYN ACK packet from the opposite host, or until enough time has +passed that it decides to give up.

+ +

Here’s an example of a really simple client using blocking network calls. It +opens a connection to www.google.com, sends it a simple HTTP request, and +prints the response to stdout.

+ +

Example: A simple blocking HTTP client

+ +
/* For sockaddr_in */
+#include <netinet/in.h>
+/* For socket functions */
+#include <sys/socket.h>
+/* For gethostbyname */
+#include <netdb.h>
+
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+
+int main(int c, char **v)
+{
+    const char query[] =
+        "GET / HTTP/1.0\r\n"
+        "Host: www.google.com\r\n"
+        "\r\n";
+    const char hostname[] = "www.google.com";
+    struct sockaddr_in sin;
+    struct hostent *h;
+    const char *cp;
+    int fd;
+    ssize_t n_written, remaining;
+    char buf[1024];
+
+    /* Look up the IP address for the hostname.
+     * Watch out; this isn't threadsafe on most platforms.
+     */
+    h = gethostbyname(hostname);
+    if (!h) {
+        fprintf(stderr, "Couldn't lookup %s: %s", hostname, hstrerror(h_errno));
+        return 1;
+    }
+    if (h->h_addrtype != AF_INET) {
+        fprintf(stderr, "No ipv6 support, sorry.");
+        return 1;
+    }
+
+    /* Allocate a new socket */
+    fd = socket(AF_INET, SOCK_STREAM, 0);
+    if (fd < 0) {
+        perror("socket");
+        return 1;
+    }
+
+    /* Connect to the remote host. */
+    sin.sin_family = AF_INET;
+    sin.sin_port = htons(80);
+    sin.sin_addr = *(struct in_addr*)h->h_addr;
+    if (connect(fd, (struct sockaddr*) &sin, sizeof(sin))) {
+        perror("connect");
+        close(fd);
+        return 1;
+    }
+
+    /* Write the query. */
+    /* XXX Can send succeed partially? */
+    cp = query;
+    remaining = strlen(query);
+    while (remaining) {
+      n_written = send(fd, cp, remaining, 0);
+      if (n_written <= 0) {
+        perror("send");
+        return 1;
+      }
+      remaining -= n_written;
+      cp += n_written;
+    }
+
+    /* Get an answer back. */
+    while (1) {
+        ssize_t result = recv(fd, buf, sizeof(buf), 0);
+        if (result == 0) {
+            break;
+        } else if (result < 0) {
+            perror("recv");
+            close(fd);
+            return 1;
+        }
+        fwrite(buf, 1, result, stdout);
+    }
+
+    close(fd);
+    return 0;
+}
+
+ +

All of the network calls in the code above are blocking: the gethostbyname +does not return until it has succeeded or failed in resolving www.google.com; +the connect does not return until it has connected; the recv calls do not +return until they have received data or a close; and the send call does not +return until it has at least flushed its output to the kernel’s write buffers.

+ +

Now, blocking IO is not necessarily evil. If there’s nothing else you wanted +your program to do in the meantime, blocking IO will work fine for you. But +suppose that you need to write a program to handle multiple connections at once. +To make our example concrete: suppose that you want to read input from two +connections, and you don’t know which connection will get input first. You +can’t say because if data arrives on fd[2] first, your program won’t even +try reading from ` fd[2]until the reads from `fd[0] and fd[1]` have +gotten some data and finished.

+ +

Bad Example

+ +
/* This won't work. */
+char buf[1024];
+int i, n;
+while (i_still_want_to_read()) {
+    for (i=0; i<n_sockets; ++i) {
+        n = recv(fd[i], buf, sizeof(buf), 0);
+        if (n==0)
+            handle_close(fd[i]);
+        else if (n<0)
+            handle_error(fd[i], errno);
+        else
+            handle_input(fd[i], buf, n);
+    }
+}
+
+ +

Sometimes people solve this problem with multithreading, or with multi-process +servers. One of the simplest ways to do multithreading is with a separate +process (or thread) to deal with each connection. Since each connection has its +own process, a blocking IO call that waits for one connection won’t make any of +the other connections’ processes block.

+ +

Here’s another example program. It is a trivial server that listens for TCP +connections on port 40713, reads data from its input one line at a time, and +writes out the ROT13 obfuscation of line each as it arrives. It uses the Unix +fork() call to create a new process for each incoming connection.

+ +

Example: Forking ROT13 server

+ +
/* For sockaddr_in */
+#include <netinet/in.h>
+/* For socket functions */
+#include <sys/socket.h>
+
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define MAX_LINE 16384
+
+char
+rot13_char(char c)
+{
+    /* We don't want to use isalpha here; setting the locale would change
+     * which characters are considered alphabetical. */
+    if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
+        return c + 13;
+    else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
+        return c - 13;
+    else
+        return c;
+}
+
+void
+child(int fd)
+{
+    char outbuf[MAX_LINE+1];
+    size_t outbuf_used = 0;
+    ssize_t result;
+
+    while (1) {
+        char ch;
+        result = recv(fd, &ch, 1, 0);
+        if (result == 0) {
+            break;
+        } else if (result == -1) {
+            perror("read");
+            break;
+        }
+
+        /* We do this test to keep the user from overflowing the buffer. */
+        if (outbuf_used < sizeof(outbuf)) {
+            outbuf[outbuf_used++] = rot13_char(ch);
+        }
+
+        if (ch == '\n') {
+            send(fd, outbuf, outbuf_used, 0);
+            outbuf_used = 0;
+            continue;
+        }
+    }
+}
+
+void
+run(void)
+{
+    int listener;
+    struct sockaddr_in sin;
+
+    sin.sin_family = AF_INET;
+    sin.sin_addr.s_addr = 0;
+    sin.sin_port = htons(40713);
+
+    listener = socket(AF_INET, SOCK_STREAM, 0);
+
+#ifndef WIN32
+    {
+        int one = 1;
+        setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+    }
+#endif
+
+    if (bind(listener, (struct sockaddr*)&sin, sizeof(sin)) < 0) {
+        perror("bind");
+        return;
+    }
+
+    if (listen(listener, 16)<0) {
+        perror("listen");
+        return;
+    }
+
+
+
+    while (1) {
+        struct sockaddr_storage ss;
+        socklen_t slen = sizeof(ss);
+        int fd = accept(listener, (struct sockaddr*)&ss, &slen);
+        if (fd < 0) {
+            perror("accept");
+        } else {
+            if (fork() == 0) {
+                child(fd);
+                exit(0);
+            }
+        }
+    }
+}
+
+int
+main(int c, char **v)
+{
+    run();
+    return 0;
+}
+
+ +

So, do we have the perfect solution for handling multiple connections at once? +Can I stop writing this book and go work on something else now? Not quite. +First off, process creation (and even thread creation) can be pretty expensive +on some platforms. In real life, you’d want to use a thread pool instead of +creating new processes. But more fundamentally, threads won’t scale as much as +you’d like. If your program needs to handle thousands or tens of thousands of +connections at a time, dealing with tens of thousands of threads will not be +as efficient as trying to have only a few threads per CPU.

+ +

But if threading isn’t the answer to having multiple connections, what is? In +the Unix paradigm, you make your sockets nonblocking. The Unix call to do this +is:

+ +
fcntl(fd, F_SETFL, O_NONBLOCK);
+
+ +

where fd is the file descriptor for the socket. 1 Once you’ve +made fd (the socket) nonblocking, from then on, whenever you make a network +call to fd the call will either complete the operation immediately or return +with a special error code to indicate “I couldn’t make any progress now, try +again.” So our two-socket example might be naively written as:

+ +

Bad Example: busy-polling all sockets

+ +
/* This will work, but the performance will be unforgivably bad. */
+int i, n;
+char buf[1024];
+for (i=0; i < n_sockets; ++i)
+    fcntl(fd[i], F_SETFL, O_NONBLOCK);
+
+while (i_still_want_to_read()) {
+    for (i=0; i < n_sockets; ++i) {
+        n = recv(fd[i], buf, sizeof(buf), 0);
+        if (n == 0) {
+            handle_close(fd[i]);
+        } else if (n < 0) {
+            if (errno == EAGAIN)
+                 ; /* The kernel didn't have any data for us to read. */
+            else
+                 handle_error(fd[i], errno);
+         } else {
+            handle_input(fd[i], buf, n);
+         }
+    }
+}
+
+ +

Now that we’re using nonblocking sockets, the code above would work… but only +barely. The performance will be awful, for two reasons. First, when there is +no data to read on either connection the loop will spin indefinitely, using up +all your CPU cycles. Second, if you try to handle more than one or two +connections with this approach you’ll do a kernel call for each one, whether +it has any data for you or not. So what we need is a way to tell the kernel +“wait until one of these sockets is ready to give me some data, and tell me +which ones are ready.”

+ +

The oldest solution that people still use for this problem is select(). The +select() call takes three sets of fds (implemented as bit arrays): one for +reading, one for writing, and one for “exceptions”. It waits until a socket +from one of the sets is ready and alters the sets to contain only the sockets +ready for use.

+ +

Here is our example again, using select:

+ +

Example: Using select

+ +
/* If you only have a couple dozen fds, this version won't be awful */
+fd_set readset;
+int i, n;
+char buf[1024];
+
+while (i_still_want_to_read()) {
+    int maxfd = -1;
+    FD_ZERO(&readset);
+
+    /* Add all of the interesting fds to readset */
+    for (i=0; i < n_sockets; ++i) {
+         if (fd[i]>maxfd) maxfd = fd[i];
+         FD_SET(fd[i], &readset);
+    }
+
+    /* Wait until one or more fds are ready to read */
+    select(maxfd+1, &readset, NULL, NULL, NULL);
+
+    /* Process all of the fds that are still set in readset */
+    for (i=0; i < n_sockets; ++i) {
+        if (FD_ISSET(fd[i], &readset)) {
+            n = recv(fd[i], buf, sizeof(buf), 0);
+            if (n == 0) {
+                handle_close(fd[i]);
+            } else if (n < 0) {
+                if (errno == EAGAIN)
+                     ; /* The kernel didn't have any data for us to read. */
+                else
+                     handle_error(fd[i], errno);
+             } else {
+                handle_input(fd[i], buf, n);
+             }
+        }
+    }
+}
+
+ +

And here’s a reimplementation of our ROT13 server, using select() this time.

+ +

Example: select()-based ROT13 server

+ +
/* For sockaddr_in */
+#include <netinet/in.h>
+/* For socket functions */
+#include <sys/socket.h>
+/* For fcntl */
+#include <fcntl.h>
+/* for select */
+#include <sys/select.h>
+
+#include <assert.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define MAX_LINE 16384
+
+char
+rot13_char(char c)
+{
+    /* We don't want to use isalpha here; setting the locale would change
+     * which characters are considered alphabetical. */
+    if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
+        return c + 13;
+    else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
+        return c - 13;
+    else
+        return c;
+}
+
+struct fd_state {
+    char buffer[MAX_LINE];
+    size_t buffer_used;
+
+    int writing;
+    size_t n_written;
+    size_t write_upto;
+};
+
+struct fd_state *
+alloc_fd_state(void)
+{
+    struct fd_state *state = malloc(sizeof(struct fd_state));
+    if (!state)
+        return NULL;
+    state->buffer_used = state->n_written = state->writing =
+        state->write_upto = 0;
+    return state;
+}
+
+void
+free_fd_state(struct fd_state *state)
+{
+    free(state);
+}
+
+void
+make_nonblocking(int fd)
+{
+    fcntl(fd, F_SETFL, O_NONBLOCK);
+}
+
+int
+do_read(int fd, struct fd_state *state)
+{
+    char buf[1024];
+    int i;
+    ssize_t result;
+    while (1) {
+        result = recv(fd, buf, sizeof(buf), 0);
+        if (result <= 0)
+            break;
+
+        for (i=0; i < result; ++i)  {
+            if (state->buffer_used < sizeof(state->buffer))
+                state->buffer[state->buffer_used++] = rot13_char(buf[i]);
+            if (buf[i] == '\n') {
+                state->writing = 1;
+                state->write_upto = state->buffer_used;
+            }
+        }
+    }
+
+    if (result == 0) {
+        return 1;
+    } else if (result < 0) {
+        if (errno == EAGAIN)
+            return 0;
+        return -1;
+    }
+
+    return 0;
+}
+
+int
+do_write(int fd, struct fd_state *state)
+{
+    while (state->n_written < state->write_upto) {
+        ssize_t result = send(fd, state->buffer + state->n_written,
+                              state->write_upto - state->n_written, 0);
+        if (result < 0) {
+            if (errno == EAGAIN)
+                return 0;
+            return -1;
+        }
+        assert(result != 0);
+
+        state->n_written += result;
+    }
+
+    if (state->n_written == state->buffer_used)
+        state->n_written = state->write_upto = state->buffer_used = 0;
+
+    state->writing = 0;
+
+    return 0;
+}
+
+void
+run(void)
+{
+    int listener;
+    struct fd_state *state[FD_SETSIZE];
+    struct sockaddr_in sin;
+    int i, maxfd;
+    fd_set readset, writeset, exset;
+
+    sin.sin_family = AF_INET;
+    sin.sin_addr.s_addr = 0;
+    sin.sin_port = htons(40713);
+
+    for (i = 0; i < FD_SETSIZE; ++i)
+        state[i] = NULL;
+
+    listener = socket(AF_INET, SOCK_STREAM, 0);
+    make_nonblocking(listener);
+
+#ifndef WIN32
+    {
+        int one = 1;
+        setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+    }
+#endif
+
+    if (bind(listener, (struct sockaddr*)&sin, sizeof(sin)) < 0) {
+        perror("bind");
+        return;
+    }
+
+    if (listen(listener, 16)<0) {
+        perror("listen");
+        return;
+    }
+
+    FD_ZERO(&readset);
+    FD_ZERO(&writeset);
+    FD_ZERO(&exset);
+
+    while (1) {
+        maxfd = listener;
+
+        FD_ZERO(&readset);
+        FD_ZERO(&writeset);
+        FD_ZERO(&exset);
+
+        FD_SET(listener, &readset);
+
+        for (i=0; i < FD_SETSIZE; ++i) {
+            if (state[i]) {
+                if (i > maxfd)
+                    maxfd = i;
+                FD_SET(i, &readset);
+                if (state[i]->writing) {
+                    FD_SET(i, &writeset);
+                }
+            }
+        }
+
+        if (select(maxfd+1, &readset, &writeset, &exset, NULL) < 0) {
+            perror("select");
+            return;
+        }
+
+        if (FD_ISSET(listener, &readset)) {
+            struct sockaddr_storage ss;
+            socklen_t slen = sizeof(ss);
+            int fd = accept(listener, (struct sockaddr*)&ss, &slen);
+            if (fd < 0) {
+                perror("accept");
+            } else if (fd > FD_SETSIZE) {
+                close(fd);
+            } else {
+                make_nonblocking(fd);
+                state[fd] = alloc_fd_state();
+                assert(state[fd]);/*XXX*/
+            }
+        }
+
+        for (i=0; i < maxfd+1; ++i) {
+            int r = 0;
+            if (i == listener)
+                continue;
+
+            if (FD_ISSET(i, &readset)) {
+                r = do_read(i, state[i]);
+            }
+            if (r == 0 && FD_ISSET(i, &writeset)) {
+                r = do_write(i, state[i]);
+            }
+            if (r) {
+                free_fd_state(state[i]);
+                state[i] = NULL;
+                close(i);
+            }
+        }
+    }
+}
+
+int
+main(int c, char **v)
+{
+    setvbuf(stdout, NULL, _IONBF, 0);
+
+    run();
+    return 0;
+}
+
+ +

But we’re still not done. Because generating and reading the select() bit +arrays takes time proportional to the largest fd that you provided for +select(), the select() call scales terribly when the number of sockets is +high. 2

+ +

Different operating systems have provided different replacement functions for +select. These include poll(), epoll(), kqueue(), evports, and +/dev/poll. All of these give better performance than select(), and all but +poll() give O(1) performance for adding a socket, removing a socket, and for +noticing that a socket is ready for IO.

+ +

Unfortunately, none of the efficient interfaces is a ubiquitous standard. Linux +has epoll(), the BSDs (including Darwin) have kqueue(), Solaris has +evports and /dev/poll… and none of these operating systems has any of the +others. So if you want to write a portable high-performance asynchronous +application, you’ll need an abstraction that wraps all of these interfaces, and +provides whichever one of them is the most efficient.

+ +

And that’s what the lowest level of the Libevent API does for you. It provides +a consistent interface to various select() replacements, using the most +efficient version available on the computer where it’s running.

+ +

Here’s yet another version of our asynchronous ROT13 server. This time, it +uses Libevent 2 instead of select(). Note that the fd_sets are gone now: +instead, we associate and disassociate events with a struct event_base, which +might be implemented in terms of select(), poll(), epoll(), kqueue(), +etc.

+ +

Example: A low-level ROT13 server with Libevent

+ +
/* For sockaddr_in */
+#include <netinet/in.h>
+/* For socket functions */
+#include <sys/socket.h>
+/* For fcntl */
+#include <fcntl.h>
+
+#include <event2/event.h>
+
+#include <assert.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define MAX_LINE 16384
+
+void do_read(evutil_socket_t fd, short events, void *arg);
+void do_write(evutil_socket_t fd, short events, void *arg);
+
+char
+rot13_char(char c)
+{
+    /* We don't want to use isalpha here; setting the locale would change
+     * which characters are considered alphabetical. */
+    if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
+        return c + 13;
+    else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
+        return c - 13;
+    else
+        return c;
+}
+
+struct fd_state {
+    char buffer[MAX_LINE];
+    size_t buffer_used;
+
+    size_t n_written;
+    size_t write_upto;
+
+    struct event *read_event;
+    struct event *write_event;
+};
+
+struct fd_state *
+alloc_fd_state(struct event_base *base, evutil_socket_t fd)
+{
+    struct fd_state *state = malloc(sizeof(struct fd_state));
+    if (!state)
+        return NULL;
+    state->read_event = event_new(base, fd, EV_READ|EV_PERSIST, do_read, state);
+    if (!state->read_event) {
+        free(state);
+        return NULL;
+    }
+    state->write_event =
+        event_new(base, fd, EV_WRITE|EV_PERSIST, do_write, state);
+
+    if (!state->write_event) {
+        event_free(state->read_event);
+        free(state);
+        return NULL;
+    }
+
+    state->buffer_used = state->n_written = state->write_upto = 0;
+
+    assert(state->write_event);
+    return state;
+}
+
+void
+free_fd_state(struct fd_state *state)
+{
+    event_free(state->read_event);
+    event_free(state->write_event);
+    free(state);
+}
+
+void
+do_read(evutil_socket_t fd, short events, void *arg)
+{
+    struct fd_state *state = arg;
+    char buf[1024];
+    int i;
+    ssize_t result;
+    while (1) {
+        assert(state->write_event);
+        result = recv(fd, buf, sizeof(buf), 0);
+        if (result <= 0)
+            break;
+
+        for (i=0; i < result; ++i)  {
+            if (state->buffer_used < sizeof(state->buffer))
+                state->buffer[state->buffer_used++] = rot13_char(buf[i]);
+            if (buf[i] == '\n') {
+                assert(state->write_event);
+                event_add(state->write_event, NULL);
+                state->write_upto = state->buffer_used;
+            }
+        }
+    }
+
+    if (result == 0) {
+        free_fd_state(state);
+    } else if (result < 0) {
+        if (errno == EAGAIN) // XXXX use evutil macro
+            return;
+        perror("recv");
+        free_fd_state(state);
+    }
+}
+
+void
+do_write(evutil_socket_t fd, short events, void *arg)
+{
+    struct fd_state *state = arg;
+
+    while (state->n_written < state->write_upto) {
+        ssize_t result = send(fd, state->buffer + state->n_written,
+                              state->write_upto - state->n_written, 0);
+        if (result < 0) {
+            if (errno == EAGAIN) // XXX use evutil macro
+                return;
+            free_fd_state(state);
+            return;
+        }
+        assert(result != 0);
+
+        state->n_written += result;
+    }
+
+    if (state->n_written == state->buffer_used)
+        state->n_written = state->write_upto = state->buffer_used = 1;
+
+    event_del(state->write_event);
+}
+
+void
+do_accept(evutil_socket_t listener, short event, void *arg)
+{
+    struct event_base *base = arg;
+    struct sockaddr_storage ss;
+    socklen_t slen = sizeof(ss);
+    int fd = accept(listener, (struct sockaddr*)&ss, &slen);
+    if (fd < 0) { // XXXX eagain??
+        perror("accept");
+    } else if (fd > FD_SETSIZE) {
+        close(fd); // XXX replace all closes with EVUTIL_CLOSESOCKET */
+    } else {
+        struct fd_state *state;
+        evutil_make_socket_nonblocking(fd);
+        state = alloc_fd_state(base, fd);
+        assert(state); /*XXX err*/
+        assert(state->write_event);
+        event_add(state->read_event, NULL);
+    }
+}
+
+void
+run(void)
+{
+    evutil_socket_t listener;
+    struct sockaddr_in sin;
+    struct event_base *base;
+    struct event *listener_event;
+
+    base = event_base_new();
+    if (!base)
+        return; /*XXXerr*/
+
+    sin.sin_family = AF_INET;
+    sin.sin_addr.s_addr = 0;
+    sin.sin_port = htons(40713);
+
+    listener = socket(AF_INET, SOCK_STREAM, 0);
+    evutil_make_socket_nonblocking(listener);
+
+#ifndef WIN32
+    {
+        int one = 1;
+        setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+    }
+#endif
+
+    if (bind(listener, (struct sockaddr*)&sin, sizeof(sin)) < 0) {
+        perror("bind");
+        return;
+    }
+
+    if (listen(listener, 16)<0) {
+        perror("listen");
+        return;
+    }
+
+    listener_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base);
+    /*XXX check it */
+    event_add(listener_event, NULL);
+
+    event_base_dispatch(base);
+}
+
+int
+main(int c, char **v)
+{
+    setvbuf(stdout, NULL, _IONBF, 0);
+
+    run();
+    return 0;
+}
+
+ +

NOTE:

+ +

Other things to note in the code

+ +

instead of typing the sockets as “int”, we’re using the type +evutil_socket_t. Instead of calling fcntl(O_NONBLOCK) to make the sockets +nonblocking, we’re calling evutil_make_socket_nonblocking. These changes +make our code compatible with the divergent parts of the Win32 networking +API.

+ +

What about convenience? (and what about Windows?)

+ +

You’ve probably noticed that as our code has gotten more efficient, it has also +gotten more complex. Back when we were forking, we didn’t have to manage a +buffer for each connection: we just had a separate stack-allocated buffer for +each process. We didn’t need to explicitly track whether each socket was +reading or writing: that was implicit in our location in the code. And we +didn’t need a structure to track how much of each operation had completed: we +just used loops and stack variables.

+ +

Moreover, if you’re deeply experienced with networking on Windows, you’ll +realize that Libevent probably isn’t getting optimal performance when it’s +used as in the example above. On Windows, the way you do fast asynchronous IO +is not with a select()-like interface: it’s by using the IOCP (IO Completion +Ports) API. Unlike all the fast networking APIs, IOCP does not alert your +program when a socket is ready for an operation that your program then has to +perform. Instead, the program tells the Windows networking stack to start a +network operation, and IOCP tells the program when the operation has finished.

+ +

Fortunately, the Libevent 2 “bufferevents” interface solves both of these +issues: it makes programs much simpler to write, and provides an interface that +Libevent can implement efficiently on Windows and on Unix.

+ +

Here’s our ROT13 server one last time, using the bufferevents API.

+ +

Example: A simpler ROT13 server with Libevent

+ +
/* For sockaddr_in */
+#include <netinet/in.h>
+/* For socket functions */
+#include <sys/socket.h>
+/* For fcntl */
+#include <fcntl.h>
+
+#include <event2/event.h>
+#include <event2/buffer.h>
+#include <event2/bufferevent.h>
+
+#include <assert.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define MAX_LINE 16384
+
+void do_read(evutil_socket_t fd, short events, void *arg);
+void do_write(evutil_socket_t fd, short events, void *arg);
+
+char
+rot13_char(char c)
+{
+    /* We don't want to use isalpha here; setting the locale would change
+     * which characters are considered alphabetical. */
+    if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
+        return c + 13;
+    else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
+        return c - 13;
+    else
+        return c;
+}
+
+void
+readcb(struct bufferevent *bev, void *ctx)
+{
+    struct evbuffer *input, *output;
+    char *line;
+    size_t n;
+    int i;
+    input = bufferevent_get_input(bev);
+    output = bufferevent_get_output(bev);
+
+    while ((line = evbuffer_readln(input, &n, EVBUFFER_EOL_LF))) {
+        for (i = 0; i < n; ++i)
+            line[i] = rot13_char(line[i]);
+        evbuffer_add(output, line, n);
+        evbuffer_add(output, "\n", 1);
+        free(line);
+    }
+
+    if (evbuffer_get_length(input) >= MAX_LINE) {
+        /* Too long; just process what there is and go on so that the buffer
+         * doesn't grow infinitely long. */
+        char buf[1024];
+        while (evbuffer_get_length(input)) {
+            int n = evbuffer_remove(input, buf, sizeof(buf));
+            for (i = 0; i < n; ++i)
+                buf[i] = rot13_char(buf[i]);
+            evbuffer_add(output, buf, n);
+        }
+        evbuffer_add(output, "\n", 1);
+    }
+}
+
+void
+errorcb(struct bufferevent *bev, short error, void *ctx)
+{
+    if (error & BEV_EVENT_EOF) {
+        /* connection has been closed, do any clean up here */
+        /* ... */
+    } else if (error & BEV_EVENT_ERROR) {
+        /* check errno to see what error occurred */
+        /* ... */
+    } else if (error & BEV_EVENT_TIMEOUT) {
+        /* must be a timeout event handle, handle it */
+        /* ... */
+    }
+    bufferevent_free(bev);
+}
+
+void
+do_accept(evutil_socket_t listener, short event, void *arg)
+{
+    struct event_base *base = arg;
+    struct sockaddr_storage ss;
+    socklen_t slen = sizeof(ss);
+    int fd = accept(listener, (struct sockaddr*)&ss, &slen);
+    if (fd < 0) {
+        perror("accept");
+    } else if (fd > FD_SETSIZE) {
+        close(fd);
+    } else {
+        struct bufferevent *bev;
+        evutil_make_socket_nonblocking(fd);
+        bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
+        bufferevent_setcb(bev, readcb, NULL, errorcb, NULL);
+        bufferevent_setwatermark(bev, EV_READ, 0, MAX_LINE);
+        bufferevent_enable(bev, EV_READ|EV_WRITE);
+    }
+}
+
+void
+run(void)
+{
+    evutil_socket_t listener;
+    struct sockaddr_in sin;
+    struct event_base *base;
+    struct event *listener_event;
+
+    base = event_base_new();
+    if (!base)
+        return; /*XXXerr*/
+
+    sin.sin_family = AF_INET;
+    sin.sin_addr.s_addr = 0;
+    sin.sin_port = htons(40713);
+
+    listener = socket(AF_INET, SOCK_STREAM, 0);
+    evutil_make_socket_nonblocking(listener);
+
+#ifndef WIN32
+    {
+        int one = 1;
+        setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+    }
+#endif
+
+    if (bind(listener, (struct sockaddr*)&sin, sizeof(sin)) < 0) {
+        perror("bind");
+        return;
+    }
+
+    if (listen(listener, 16)<0) {
+        perror("listen");
+        return;
+    }
+
+    listener_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base);
+    /*XXX check it */
+    event_add(listener_event, NULL);
+
+    event_base_dispatch(base);
+}
+
+int
+main(int c, char **v)
+{
+    setvbuf(stdout, NULL, _IONBF, 0);
+
+    run();
+    return 0;
+}
+
+ +

How efficient is all of this, really?

+ +

XXXX write an efficiency section here. The benchmarks on the libevent page are +really out of date.

+ +

1 A file descriptor is the number the kernel assigns to the +socket when you open it. You use this number to make Unix calls referring to +the socket. +2 On the userspace side, generating and reading the bit arrays +can be made to take time proportional to the number of fds that you provided +for select(). But on the kernel side, reading the bit arrays takes time +proportional to the largest fd in the bit array, which tends to be around the +total number of fds in use in the whole program, regardless of how many fds +are added to the sets in select().

+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/09/14/Memcached-earliest-version.html b/2015/09/14/Memcached-earliest-version.html new file mode 100644 index 0000000..3d161ee --- /dev/null +++ b/2015/09/14/Memcached-earliest-version.html @@ -0,0 +1,1358 @@ + + + +Memcached仓库里第一个版本解读 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Memcached仓库里第一个版本解读

  + +
+
+ + +

本文研究了一点最早期的 Memcached 源码, 希望能从这个简单的源码入手, 能更好地理解 +Memcached 的一些思想.

+ + + +

Readme 文件

+ +

Readme 文件

+ +
+

Dependencies:

+ +

– Judy, http://judy.sf.net/ +– libevent, http://www.monkey.org/~provos/libevent/

+ +

If using Linux, you need a kernel with epoll. Sure, libevent will +work with normal select, but it sucks.

+ +

epoll isn’t in Linux 2.4 yet, but there’s a backport at:

+ +

http://www.xmailserver.org/linux-patches/nio-improve.html

+ +

You want the epoll-lt patch (level-triggered).

+ +

Also, be warned that the -k (mlockall) option to memcached might be +dangerous when using a large cache. Just make sure the memcached +machines +don’t swap. memcached does non-blocking network I/O, but not disk. +(it +should never go to disk, or you’ve lost the whole point of it)

+
+ +

翻译如下

+ +
+

依赖

+ +

– judy, http://judy.sf.net/ +– libevent, http://www.monkey.org/~provos/libevent/

+ +

如果你使用 Linux, 那么最好内核能支持epoll, 当然了, libevent 也可以使用 +select, 但是性能很烂.

+ +

目前 2.4 内核还不支持epoll, 但是这里有 backport(将一个软件的补丁应用到比 +此补丁所对应的版本更老的版本的行为):

+ +

http://www.xmailserver.org/linux-patches/nio-improve.html

+ +

You want the epoll-lt patch (level-triggered). // 啥意思

+ +

额, 请注意当使用比较大的缓存时, memcached 的-k选项可能是比较危险的. 还要 +注意 memcached 机器不要有swap空间. memcached 使用非阻塞网络 I/O, 而不是磁盘. +memcached 永远不应该访问磁盘, 否则你就失去它的全部了(即好的性能啥的都没用到)

+
+ +

求轻点打: 翻译完了我自己都觉得翻译的恶心, 参照原文理解吧…

+ +

Makefile

+ +
all: memcached memcached-debug
+
+memcached: memcached.c
+    gcc-2.95  -I. -L. -static -o memcached memcached.c -levent -lJudy
+
+memcached-debug: memcached.c
+    gcc-2.95 -g  -I. -L. -static -o memcached-debug memcached.c -levent -lJudy
+
+clean:
+    rm memcached memcached-debug
+
+ +

很简单, gcc-2.95 版本, 链接时使用 event 和 judy 库.

+ +

源码

+ +

这才是大头, 虽然早期源码很简单, 但还是不贴出来了, 我们捡要紧的说.

+ +

先看几个数据结构, 源码如下:

+ +
/* 统计时使用 */
+struct stats {
+    unsigned int  curr_items;
+    unsigned int  total_items;
+    unsigned long long  curr_bytes;
+    unsigned int  curr_conns;
+    unsigned int  total_conns;
+    unsigned int  conn_structs;
+    unsigned int  get_cmds;
+    unsigned int  set_cmds;
+    unsigned int  get_hits;
+    unsigned int  get_misses;
+    unsigned long long bytes_read;
+    unsigned long long bytes_written;
+};
+
+/* 处理程序设定以及配置 */
+struct settings {
+    unsigned long long maxbytes;
+    int maxitems;
+    int maxconns;
+    int port;
+    struct in_addr interface;
+};
+
+/* 缓存数据链表 */
+typedef struct _stritem {
+    struct _stritem *next;
+    struct _stritem *prev;
+    int    usecount;
+    int    it_flags;
+    char   *key;    /* 缓存的key */
+    void   *data;   /* 缓存的数据 */
+    int    nbytes;  /* 缓存数据的大小 */
+    int    ntotal;  /* 当前结构体 + key + data的大小, 就是当前缓存占用的内存 */
+    int    flags;
+    time_t time;    /* 最后访问时间(LRU算法用到) */
+    time_t exptime; /* 过期时间 */
+    void * end[0];
+} item;
+
+/* memcached处理状态的枚举 */
+enum conn_states {
+    conn_listening,  /* 套接字正在监听连接 */
+    conn_read,       /* reading in a command line */
+    conn_write,      /* writing out a simple response */
+    conn_nread,      /* reading in a fixed number of bytes */
+    conn_closing,    /* closing this connection */
+    conn_mwrite      /* writing out many items sequentially */
+};
+
+/* 下面是最大也是最重要的一个结构体, 连接结构体 */
+typedef struct {
+    int    sfd;
+    int    state;
+    struct event event;
+    short  ev_flags;
+    short  which;  /* which events were just triggered */
+
+    char   *rbuf;
+    int    rsize;
+    int    rbytes;
+
+    char   *wbuf;
+    char   *wcurr;
+    int    wsize;
+    int    wbytes;
+    int    write_and_close; /* close after finishing current write */
+    void   *write_and_free; /* free this memory after finishing writing */
+
+    char   *rcurr;
+    int    rlbytes;
+
+    /* data for the nread state */
+
+    void   *item;     /* for commands set/add/replace  */
+    int    item_comm; /* which one is it: set/add/replace */
+
+    /* data for the mwrite state */
+    item   **ilist;   /* list of items to write out */
+    int    isize;
+    item   **icurr;
+    int    ileft;
+    int    ipart;     /* 1 if we're writing a VALUE line, 2 if we're writing data */
+    char   ibuf[256]; /* for VALUE lines */
+    char   *iptr;
+    int    ibytes;
+
+} conn;
+
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/09/17/memory-alignment.html b/2015/09/17/memory-alignment.html new file mode 100644 index 0000000..4071790 --- /dev/null +++ b/2015/09/17/memory-alignment.html @@ -0,0 +1,1708 @@ + + + +C语言内存对齐 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

C语言内存对齐

  + +
+
+ + +

转自: 21aspnet 博客

+ +

文章最后本人做了一幅图, 一看就明白了, 这个问题网上讲的不少, 但是都没有把问题说透.

+ + + +

概念

+ +

对齐跟数据在内存中的位置有关.如果一个变量的内存地址正好位于它长度的整数倍, 他 +就被称做自然对齐.比如在 32 位 cpu 下, 假设一个整型变量的地址为 0x00000004, 那它就 +是自然对齐的.

+ +

为什么要字节对齐

+ +

需要字节对齐的根本原因在于 CPU 访问数据的效率问题.假设上面整型变量的地址不是自然 +对齐, 比如为0x00000002, 则 CPU 如果取它的值的话需要访问两次内存, 第一次取从 +0x00000002-0x00000003的一个short, 第二次取从0x00000004-0x00000005的一个 +short然后组合得到所要的数据, 如果变量在0x00000003地址上的话则要访问三次内 +存, 第一次为char, 第二次为short, 第三次为char, 然后组合得到整型数据.而如 +果变量在自然对齐位置上, 则只要一次就可以取出数据.一些系统对对齐要求非常严格, +比如 sparc 系统, 如果取未对齐的数据会发生错误, 举个例:

+ +
char ch[8];
+char *p = &ch[1];
+int i = *(int *)p;
+
+ +

运行时会报segment error, 而在 x86 上就不会出现错误, 只是效率下降.

+ +

正确处理字节对齐

+ +

对于标准数据类型, 它的地址只要是它的长度的整数倍就行了, 而非标准数据类型按下面 +的原则对齐:

+ +
    +
  • 数组: 按照基本数据类型对齐, 第一个对齐了后面的自然也就对齐了.
  • +
  • 联合: 按其包含的长度最大的数据类型对齐.
  • +
  • 结构体: 结构体中每个数据类型都要对齐.
  • +
+ +

比如有如下一个结构体:

+ +
struct stu{
+    char sex;
+    int length;
+    char name[10];
+};
+
+struct stu my_stu;
+
+ +

由于在 x86 下, GCC 默认按4字节对齐, 它会在sex后面跟name后面分别填充三个和两 +个字节使length和整个结构体对齐.于是我们sizeof(my_stu)会得到长度为20, 而 +不是15.

+ +

__attribute__选项

+ +

我们可以按照自己设定的对齐大小来编译程序, GNU 使用__attribute__选项来设置, 比 +如我们想让刚才的结构按一字节对齐, 我们可以这样定义结构体

+ +
struct stu{
+    char sex;
+    int length;
+    char name[10];
+}__attribute__ ((aligned (1)));
+
+struct stu my_stu;
+
+ +

sizeof(my_stu)可以得到大小为15.

+ +

上面的定义等同于

+ +
struct stu{
+    char sex;
+    int length;
+    char name[10];
+}__attribute__ ((packed));
+
+struct stu my_stu;
+
+ +

__attribute__((packed))得变量或者结构体成员使用最小的对齐方式, 即对变量是一 +字节对齐, 对域(field)是位对齐.

+ +

什么时候需要设置对齐

+ +

在设计不同 CPU 下的通信协议时, 或者编写硬件驱动程序时寄存器的结构这两个地方都需 +要按一字节对齐.即使看起来本来就自然对齐的也要使其对齐, 以免不同的编译器生成的 +代码不一样.

+ +

快速理解

+ +

什么是字节对齐?

+ +

在 C 语言中, 结构是一种复合数据类型, 其构成元素既可以是基本数据类型(如int, +long, float等)的变量, 也可以是一些复合数据类型(如数组, 结构, 联合等)的数 +据单元. 在结构中, 编译器为结构的每个成员按其自然边界(alignment)分配空间. 各个 +成员按照它们被声明的顺序在内存中顺序存储, 第一个成员的地址和整个结构的地址相同.

+ +

为了使 CPU 能够对变量进行快速的访问, 变量的起始地址应该具有某些特性, 即所谓的 +“对齐”. 比如4字节的int型, 其起始地址应该位于4字节的边界上, 即起始地址能够 +被4整除.

+ +

字节对齐有什么作用?

+ +

字节对齐的作用不仅是便于 cpu 快速访问, 同时合理的利用字节对齐可以有效地节省存储空 +间.

+ +

对于 32 位机来说, 4字节对齐能够使 cpu 访问速度提高, 比如说一个long类型的变量, +如果跨越了4字节边界存储, 那么 cpu 要读取两次, 这样效率就低了. 但是在 32 位机中使 +用1字节或者2字节对齐, 反而会使变量访问速度降低. 所以这要考虑处理器类型, 另 +外还得考虑编译器的类型. 在 vc 中默认是4字节对齐的, GNU gcc 也是默认4字节对齐.

+ +

更改 C 编译器的缺省字节对齐方式

+ +

在缺省情况下, C 编译器为每一个变量或是数据单元按其自然对界条件分配空间. 一般地, +可以通过下面的方法来改变缺省的对界条件:

+ +
    +
  • 使用伪指令#pragma pack (n), C 编译器将按照n个字节对齐.
  • +
  • 使用伪指令#pragma pack (), 取消自定义字节对齐方式.
  • +
+ +

另外, 还有如下的一种方式:

+ +
    +
  • __attribute((aligned (n))), 让所作用的结构成员对齐在n字节自然边界上. 如果 +结构中有成员的长度大于n, 则按照最大成员的长度来对齐.
  • +
  • __attribute__ ((packed)), 取消结构在编译过程中的优化对齐, 按照实际占用字节 +数进行对齐.
  • +
+ +

举例说明

+ +

例 1

+ +
struct test {
+    char x1;
+    short x2;
+    float x3;
+    char x4;
+};
+
+ +

由于编译器默认情况下会对这个struct作自然边界(有人说”自然对界”我觉得边界更顺 +口)对齐, 结构的第一个成员x1, 其偏移地址为0, 占据了第 1 个字节. 第二个成员x2 +为short类型, 其起始地址必须 2 字节对界, 因此, 编译器在 x2 和 x1 之间填充了一个空字 +节. 结构的第三个成员x3和第四个成员x4恰好落在其自然边界地址上, 在它们前面不 +需要额外的填充字节. 在test结构中, 成员x3要求 4 字节对界, 是该结构所有成员中要 +求的最大边界单元, 因而test结构的自然对界条件为 4 字节, 编译器在成员x4后面填充 +了 3 个空字节. 整个结构所占据空间为 12 字节.

+ +

例 2

+ +
#pragma pack(1) /* 让编译器对这个结构作1字节对齐 */
+
+struct test {
+    char x1;
+    short x2;
+    float x3;
+    char x4;
+};
+
+#pragma pack() /* 取消1字节对齐, 恢复为默认4字节对齐 */
+
+ +

这时候sizeof(struct test)的值为 8.

+ +

例 3

+ +
#define GNUC_PACKED __attribute__((packed))
+
+struct PACKED test {
+    char x1;
+    short x2;
+    float x3;
+    char x4;
+} GNUC_PACKED;
+
+ +

这时候sizeof(struct test)的值仍为 8.

+ +

深入理解

+ +

什么是字节对齐,为什么要对齐?

+ +

现代计算机中内存空间都是按照byte划分的, 从理论上讲似乎对任何类型的变量的访问 +可以从任何地址开始, 但实际情况是在访问特定类型变量的时候经常在特定的内存地址访 +问, 这就需要各种类型数据按照一定的规则在空间上排列, 而不是顺序的一个接一个的排 +放, 这就是对齐.

+ +

对齐的作用和原因: 各个硬件平台对存储空间的处理上有很大的不同.一些平台对某些特定 +类型的数据只能从某些特定地址开始存取. 比如有些架构的 CPU 在访问一个没有进行对齐的 +变量的时候会发生错误, 那么在这种架构下编程必须保证字节对齐. 其他平台可能没有这 +种情况, 但是最常见的是如果不按照适合其平台要求对数据存放进行对齐, 会在存取效率 +上带来损失. 比如有些平台每次读都是从偶地址开始, 如果一个int型(假设为 32 位系统) +如果存放在偶地址开始的地方, 那么一个读周期就可以读出这 32bit, 而如果存放在奇地址 +开始的地方, 就需要 2 个读周期, 并对两次读出的结果的高低字节进行拼凑才能得到该 +32bit 数据.显然在读取效率上下降很多.

+ +

字节对齐对程序的影响:

+ +

先让我们看几个例子吧(32bit,x86 环境,gcc 编译器), 设结构体如下定义:

+ +
struct A {
+    int a;
+    char b;
+    short c;
+};
+
+struct B {
+    char b;
+    int a;
+    hort c;
+};
+
+ +

现在已知 32 位机器上各种数据类型的长度如下:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
类型长度备注
char1有符号无符号同
short2有符号无符号同
int4有符号无符号同
long4有符号无符号同
float4 
double8 
+ +

那么上面两个结构大小如何呢? 结果是:

+ +
    +
  1. sizeof(strcut A)值为 8
  2. +
  3. sizeof(struct B)的值却是 12
  4. +
+ +

结构体 A 中包含了 4 字节长度的int一个, 1 字节长度的char一个和 2 字节长度的short +型数据一个, B 也一样; 按理说 A, B 大小应该都是 7 字节.

+ +

之所以出现上面的结果是因为编译器要对数据成员在空间上进行对齐. 上面是按照编译器 +的默认设置进行对齐的结果, 那么我们是不是可以改变编译器的这种默认对齐设置呢, 当 +然可以. 例如:

+ +
#pragma pack (2) /*指定按2字节对齐*/
+struct C {
+    char b;
+    int a;
+    short c;
+};
+
+#pragma pack () /*取消指定对齐, 恢复缺省对齐*/
+
+ +

sizeof(struct C)值是 8.

+ +

修改对齐值为 1:

+ +
#pragma pack (1) /*指定按1字节对齐*/
+struct D {
+    char b;
+    int a;
+    short c;
+};
+#pragma pack () /*取消指定对齐, 恢复缺省对齐*/
+
+ +

sizeof(struct D)值为 7.

+ +

后面我们再讲解#pragma pack()的作用.

+ +

编译器是按照什么样的原则进行对齐的?

+ +

先让我们看四个重要的基本概念:

+ +
    +
  1. +

    数据类型自身的对齐值

    + +

    对于char型数据, 其自身对齐值为 1, 对于short型为 2, 对于int, float, +double类型, 其自身对齐值为 4, 单位字节.

    +
  2. +
  3. +

    结构体或者类的自身对齐值

    + +

    其成员中自身对齐值最大的那个值.

    +
  4. +
  5. +

    指定对齐值

    + +

    #pragma pack (value)时的指定对齐值value.

    +
  6. +
  7. +

    数据成员, 结构体和类的有效对齐值:

    + +

    自身对齐值和指定对齐值中小的那个值.

    +
  8. +
+ +

有了这些值, 我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式. 有效 +对齐值 N 是最终用来决定数据存放地址方式的值, 最重要. 有效对齐 N, 就是表示 +“对齐在 N 上”, 也就是说该数据的存放起始地址 % N = 0. 而数据结构中的数据变量都是 +按定义的先后顺序来排放的. 第一个数据变量的起始地址就是数据结构的起始地址. 结构 +体的成员变量要对齐排放, 结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变 +量占用总长度需要是对结构体有效对齐值的整数倍, 结合下面例子理解). 这样就不难理解 +上面的几个例子的值了.

+ +

例子分析

+ +

分析例子 B

+ +
struct B {
+    char b;
+    int a;
+    short c;
+};
+
+ +

假设 B 从地址空间0x0000开始排放. 该例子中没有定义指定对齐值, 在笔者环境下, 该值 +默认为 4. 第一个成员变量 b 的自身对齐值是 1, 比指定或者默认指定对齐值 4 小, 所以其有 +效对齐值为 1, 所以其存放地址0x0000符合0x0000 % 1 = 0. 第二个成员变量a, 其 +自身对齐值为 4, 所以有效对齐值也为 4, 所以只能存放在起始地址为0x00040x0007 +这四个连续的字节空间中, 复核0x0004 % 4 = 0, 且紧靠第一个变量. 第三个变量 c, 自 +身对齐值为 2, 所以有效对齐值也是 2, 可以存放在0x00080x0009这两个字节空间中, +符合0x0008 % 2 = 0. 所以从0x00000x0009存放的都是 B 内容. 再看数据结构 B 的 +自身对齐值为其变量中最大对齐值(这里是b)所以就是 4, 所以结构体的有效对齐值也 +是 4. 根据结构体圆整的要求, 0x00090x0000为 10 字节, (10+2) % 4 = 0. 所以 +0x0000A0x000B也为结构体 B 所占用. 故 B 从0x00000x000B共有 12 个字节, +sizeof(struct B) = 12;

+ +

其实如果就这一个就来说它已将满足字节对齐了, 因为它的起始地址是 0,因此肯定是对齐 +的, 之所以在后面补充 2 个字节, 是因为编译器为了实现结构数组的存取效率, 试想如果我 +们定义了一个结构 B 的数组, 那么第一个结构起始地址是 0 没有问题, 但是第二个结构呢? +按照数组的定义, 数组中所有元素都是紧挨着的, 如果我们不把结构的大小补充为 4 的整数 +倍, 那么下一个结构的起始地址将是 0x0000A, 这显然不能满足结构的地址对齐了, 因此我 +们要把结构补充成有效对齐大小的整数倍. 其实诸如: 对于char型数据, 其自身对齐值 +为 1, 对于short型为 2, 对于int, float, double类型, 其自身对齐值为 4, 这些 +已有类型的自身对齐值也是基于数组考虑的, 只是因为这些类型的长度已知了, 所以他们 +的自身对齐值也就已知了.

+ +

同理,分析上面例子 C

+ +
#pragma pack (2) /*指定按2字节对齐*/
+struct C {
+    char b;
+    int a;
+    short c;
+};
+#pragma pack () /*取消指定对齐, 恢复缺省对齐*/
+
+ +

第一个变量b的自身对齐值为 1, 指定对齐值为 2, 所以, 其有效对齐值为 1, 假设 C 从 +0x0000开始, 那么b存放在0x0000, 符合0x0000 % 1 = 0; 第二个变量, 自身对 +齐值为 4, 指定对齐值为 2, 所以有效对齐值为 2, 所以顺序存放在0x0002, 0x0003, +0x0004, 0x0005四个连续字节中, 符合0x0002 % 2 = 0. 第三个变量c的自身对 +齐值为 2, 所以有效对齐值为 2, 顺序存放在0x0006, 0x0007中, 符合 +0x0006 % 2 = 0. 所以从0x00000x00007共八字节存放的是 C 的变量. 又 C 的自身对 +齐值为 4, 所以 C 的有效对齐值为 2. 又8 % 2 = 0, C 只占用0x00000x0007的八个字 +节. 所以sizeof(struct C) = 8.

+ +

如何修改编译器的默认对齐值?

+ +

在编码时, 可以动态修改#pragma pack (n).

+ +

注意: 是 pragma 而不是 progma.

+ +

针对字节对齐,我们在编程中如何考虑?

+ +

如果在编程的时候要考虑节约空间的话, 那么我们只需要假定结构的首地址是 0, 然后各个 +变量按照上面的原则进行排列即可, 基本的原则就是把结构中的变量按照类型大小从小到 +大声明, 尽量减少中间的填补空间. 还有一种就是为了以空间换取时间的效率, 我们显示 +的进行填补空间进行对齐, 比如有一种使用空间换时间做法是显式的插入 reserved 成员

+ +
struct A {
+    char a;
+    char reserved[3]; /* 使用空间换时间 */
+    int b;
+}
+
+ +

reserved成员对我们的程序没有什么意义, 它只是起到填补空间以达到字节对齐的目的, +当然即使不加这个成员通常编译器也会给我们自动填补对齐, 我们自己加上它只是起到显 +式的提醒作用.

+ +

字节对齐可能带来的隐患

+ +

代码中关于对齐的隐患, 很多是隐式的. 比如在强制类型转换的时候. 例如:

+ +
unsigned int i = 0x12345678;
+unsigned char *p = NULL;
+unsigned short *p1 = NULL;
+
+p = &i;
+*p = 0x00;
+p1 = (unsigned short *)(p+1);
+*p1 = 0x0000;
+
+ +

最后两句代码, 从奇数边界去访问unsigned short型变量, 显然不符合对齐的规定.

+ +

在 x86 上, 类似的操作只会影响效率, 但是在 MIPS 或者 sparc 上, 可能就是一个 error, 因为 +它们要求必须字节对齐.

+ +

如何查找与字节对齐方面的问题:

+ +

如果出现对齐或者赋值问题首先查看

+ +
    +
  1. 编译器的 big little 端设置
  2. +
  3. 看这种体系本身是否支持非对齐访问
  4. +
  5. 如果支持看设置了对齐与否,如果没有则看访问时需要加某些特殊的修饰来标志其特殊访问操作
  6. +
+ +

举例

+ +
#include <stdio.h>
+main()
+{
+    struct A {
+        int a;
+        char b;
+        short c;
+    };
+
+    struct B {
+        char b;
+        int a;
+        short c;
+    };
+
+#pragma pack (2) /*指定按2字节对齐*/
+    struct C {
+        char b;
+        int a;
+        short c;
+    };
+#pragma pack () /*取消指定对齐, 恢复缺省对齐*/
+
+#pragma pack (1) /*指定按1字节对齐*/
+    struct D {
+        char b;
+        int a;
+        short c;
+    };
+#pragma pack ()/*取消指定对齐, 恢复缺省对齐*/
+
+    int s1=sizeof(struct A);
+    int s2=sizeof(struct B);
+    int s3=sizeof(struct C);
+    int s4=sizeof(struct D);
+
+    printf("%d\n",s1);
+    printf("%d\n",s2);
+    printf("%d\n",s3);
+    printf("%d\n",s4);
+}
+
+ +

输出

+ +
8
+12
+8
+7
+
+ +

修改代码

+ +
struct A {
+    // int a;
+    char b;
+    short c;
+};
+
+struct B {
+    char b;
+    // int a;
+    short c;
+};
+
+ +

输出

+ +
4
+4
+
+ +

输出都是 4, 说明之前的int影响对齐!

+ +

+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/09/17/sql-on-duplicate-key-update.html b/2015/09/17/sql-on-duplicate-key-update.html new file mode 100644 index 0000000..f46084b --- /dev/null +++ b/2015/09/17/sql-on-duplicate-key-update.html @@ -0,0 +1,1189 @@ + + + +SQL on duplicate key update简单使用 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

SQL on duplicate key update简单使用

  + +
+
+ + +

没有就插入, 有就更新

+ + +
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/09/19/how-to-read-source-code.html b/2015/09/19/how-to-read-source-code.html new file mode 100644 index 0000000..1815198 --- /dev/null +++ b/2015/09/19/how-to-read-source-code.html @@ -0,0 +1,1333 @@ + + + +如何阅读源代码 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

如何阅读源代码

  + +
+
+ + +

原文: https://wiki.c2.com/?TipsForReadingCode

+ +

Tips For Reading Code

+ +

提高编程技巧的一个重要方法就是阅读伟大的程序(ReadGreatPrograms). 诸如 +SelfDocumentingCode以及LiterateProgramming技术使得写出的代码更加容易阅读.

+ +

然而, 多数时候我们阅读的代码没有实现上面的标准. 那么, 有什么好办法能使得我们必 +须要理解或者学习的这种巨大的, 不结构化的, 由数十人维护的, 内部不一致的, 没有注 +释的代码更直观呢?

+ + + +

构建并执行程序

+ +

能够执行并观察它的外部行为将会对理解他的内部很有帮助. 程序文档也会很有用, 但是 +文档可能不能完全准确的描述程序行为.

+ +

能自己动手编译程序, 将会帮你发现程序都使用了那些外部库, 以及那些编译器以及连接 +器选项有效等等. 如果你能编译程序的 debug 版本, 那么你就可以通过 debugger 来逐步运 +行程序(StudyTheSourceWithaDebugger).

+ +

也要向程序添加 log 或者扩展程序的 log, 让它们告诉你他们正在做什么.

+ +

寻找高级逻辑

+ +

从程序的入口开始(比如 C/C++/Java 里面的main()函数), 找到程序是如何初始化自己, +怎么完成它的工作以及怎么退出的.

+ +

多数程序都有一个主循环(main loop), 找到它. 要注意, 若程序使用了外部库框架, +那么, 主循环有可能是库/框架的, 而不是应用程序自己的.

+ +

找到终止程序执行的条件, 这里包含非正常退出以及正常退出条件.

+ +

许多事件驱动(event-driven)的或者面向对象(OO)设计的都没有main, 但是它们仍然有 +入口.

+ +

Not always. Software for the GEOS operating systems for the C64/C128 platform +are structured as a single initialization routine (which is expected to return +to the OS after it’s done configuring its runtime environment), and a morass +of callbacks invoked under various circumstances. In a sense, a GEOS +application is nothing more than an overlay for the host OS itself, which +doubles as the system’s sole application. Indeed, any true event-driven style +of programming closely, if not exactly, resembles this. What is the +“main method” of a class? The constructor rarely is the most important method. +By extension, an event driven program’s initializer is rarely its most +important callback.

+ +

画一点流程图

+ +

呵呵, 我们都知道流程图是糟糕的设计工具, 但是在分析程序流程时它们很有用. 你不可 +能把这些信息都记在你的脑子里, 所以, 可以在阅读时画一些流程图或者状态图. Make +sure you account for both sides of a two-way branch, and all branches of +multi-way branches.

+ +

测试函数库调用

+ +

如果程序用到了外部函数库, 你要测试这些库调用, 并且读一读这些调用的文档. 这可能 +是你仅有的”real documentation”, 所以请好好利用它们.

+ +

找一些关键字

+ +

使用你的编辑器的查找特性(或者grep)来找整个源码树上那些你感兴趣的单词的位置. +比如说, 你想知道程序如何或者在哪里打开了文件, 那么搜索’open’或者’file’. 你可能 +会得到海量的答案, 但这不是坏事, (怎么译: a focused reading of the code will +result.)

+ +

对 C/C++程序来说, 常用的单词有: main, abort, exit, catch, throw, +fopen, create, signal, alarm, socket, fork, select. 其他语言里面 +也有相应的常用单词.

+ +

借助代码增强工具

+ +

有一些非常棒的文字搜索工具, 或者一些可以分析代码, 找到代码关系并生成图像的工具 +可以回答以下面向对象的问题:

+ +
    +
  • 谁调用了这个方法
  • +
  • 谁实现了这个接口或者是这个类的子类
  • +
  • 这个类的父类(超类)是谁
  • +
  • 这个类在哪里被实例化, 保存在那里, 需要什么参数, 返回什么.
  • +
  • 这个类重载了父类的什么方法
  • +
  • 在哪里这个方法作为多态调用, 也即通过基类还是接口.
  • +
+ +

References: Comprehension and Visualisation of Object-Oriented Code for +Inspections section 5.

+ +

打印出代码

+ +

显示器很少能比放着打印源码源码的空书桌舒服(Monitors can rarely beat the sheer +textual capacity of an empty table on which a printout of the source code has +been laid.). 你可以一眼看到上千 KB 的代码. 这极有助于我们把握代码的大概.

+ +

如果某页包含了对分析无足轻重的代码, 你可以把它替换掉. 你可以在打印出来的物理媒 +介上注释, 这也可以加快理解速度. 还可以圈出重要的函数或者高亮(大概就是做标记的意 +思, 都打出来了, 还怎么高亮…)变量名称.

+ +

写单元测试

+ +

This will help you prove to yourself that you understand what the code is supposed to do, what it actually does, and that you understand its limitations. +If there are no UnitTests, then you should definitely create a sufficient set of UnitTests before making any changes to the code.

+ +

Comment the Code

+ +

Throw the code under into a personal CVS or RCS repository and mark it up with your comments. As you work out your knowledge of the code, the comments will change. This can be an important step if you have to ReFactor the code later.

+ +

One of the best ways of reverse-engineering code you’re so unfamiliar with that you cannot even guess to comment is to derive HoareTriples for the code under scrutiny, and to compute a procedure’s WeakestPrecondition?. Knowing the invariants of a procedure often gives valuable clues as to its intended purpose, thus letting you derive understanding from the code in a way that merely guessing from reading a number of poorly named procedure names simply cannot. Using this technique, you might even find bugs in the code, despite your lack of understanding thereof (I know this from experience!).

+ +

Clean Up the Code

+ +

An old writing trick for refamiliarizing yourself with text you wrote a long time ago but forgot, or for analyzing someone else’s text, is to edit it as you go along. This is active reading. Rewrite it in different ways, or a more pleasing way. You may have noticed on Wiki that while refactoring or reworking a page, you come to understand the material much deeper than just by reading it. Code is not much different from writing.

+ +

Therefore, when reading code, reformat it as you go along. Realign spaces. Comment the Code, as it says above. Fix out of date comments (vis ThePalimpsestEffect). Fix spelling. Make the code conform to the coding standards. Usually code is written somewhat hastily, so having someone come along later to make the code look professional is also another benefit.

+ +

But, if you do a lot of changes, run the UnitTests! Breaking things isn’t necessarily something to be afraid of. By finding the hairier parts of the system and the dependent parts of the system (often those you won’t expect), you come to grok the system.

+ +

A good article on this technique is “Make bad code good”, at JavaWorld http://www.javaworld.com/javaworld/jw-03-2001/jw-0323-badcode.html

+ +

See also RefactoringForGrokking

+ +

I find I have to go through a lot of this reading the sample code on java.sun.com. Surprisingly (or maybe not), I usually make no progress grokking it until I delete all of the comments. Of course, the next step is to rename, by MassiveSearchAndReplace, all the variable names such as p, tc, f, etc. (in case you’re curious, processor, trackControl, format). The next thing that helps me is to split up the giant methods into various methods (i.e., pulling looped and if’d code into their own methods, etc.).

+ +

A MassiveSearchAndReplace might miss cases or change the wrong things. A real renaming with a refactoring tool, if there’s one available for the language, may be more effective.

+ +

Note that simple recompilation suffices as UnitTests for most of this… if I missed a tc, or it replaced CompletedEvent? with ComprocessorletedEvent?, or if I forgot to pass a variable into an extracted method, the compiler will throw an appropriate error.

+ +

Reading big random programs reminds me very much of exploring mazes in Angband.

+ +

I start at some random position (maybe found by grep) and work my way around by traversing callees and callers until I’ve mapped out enough of the immediate vicinity to see what’s going on and do my hack. If I explore enough directions for long enough, things start to connect up and give me a more “global” sense, but often I don’t explore so widely.

+ +

This is a great analogy. In a similar vein, debugging is like completing an ‘ascension kit’ in Nethack. There’s no certain path to tracking down your item/bug, and even once you’ve found/fixed it, there are always going to be others you could go for next, of greater or lesser importance/severity. Oh, and you could inadvertently overenchant your GDSM / blow up the system by enchanting / blindly ‘fixing’ before you take a second to make absolutely sure what you’re doing.

+ +

A suggestion: Use Aspect Browser. Aspect Browser is a tool for viewing cross-cutting code aspects in large amounts of code. I find it indispensable for navigating unfamiliar code, as a kind of super-grep. Read more about it here:

+ +

http://www-cse.ucsd.edu/users/wgg/Software/AB/

+ +

Another suggestion: CScope has saved my bacon more than once, apparently it supports Java now!. It’ll grep the codebase to do stuff like find references to nearly any symbol, determine which functions call a certain function, etc.

+ +

http://cscope.sourceforge.net/

+ +

Apply the patterns described in the ObjectOrientedReengineeringPatterns book. Seconded! A great book that deserves to be on every maintenance programmer’s (and that’s all of us) shelf.

+ +

A number of source code comprehension tools are reviewed at http://www.grok2.com/code_comprehension.html. This page is a couple of years old though, so some of the links may be broken. Personally a good code editor helps when reading code. Some thing like jedit or emacs from the free world.

+ +

See also ReadableCode, ProgramComprehension, SignatureSurvey, StudyTheSourceWithaDebugger, ReadItLikeaComputer, WhatItTakesToGrokCode, CodeAvoidance, HistoricalProgramReadingExercise

+ +
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/09/26/APUE-buffer.html b/2015/09/26/APUE-buffer.html new file mode 100644 index 0000000..118ad52 --- /dev/null +++ b/2015/09/26/APUE-buffer.html @@ -0,0 +1,1205 @@ + + + +APUE笔记 C5 缓冲 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

APUE笔记 C5 缓冲

  + +
+
+ + +

5.4 缓冲

+ +

缓冲有三种:

+ +
    +
  1. 全缓冲
  2. +
  3. 行缓冲
  4. +
  5. 无缓冲
  6. +
+ + + +
void setbuf(FILE *restrict fp, char *buf);
+
+int setvbuf(FILE *stream, char *buf, int mode, size_t size);
+
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/10/16/memcached-init.html b/2015/10/16/memcached-init.html new file mode 100644 index 0000000..ede2bfa --- /dev/null +++ b/2015/10/16/memcached-init.html @@ -0,0 +1,2091 @@ + + + +Memcached 的初始化过程 - 挚爱荒原 + + + + + + + + +
+
+
+ +
+ +
+ +
+ +
+ + + +
+

Memcached 的初始化过程

  + + +
+ + + + + +
+
+ + + +
+

version 1.2.0

+ +

整体大概分析

+ +
main 函数变量声明
+ +
int c;
+struct in_addr addr; /* TCP监听地址 */
+bool lock_memory = false; /* 是否进行内存锁定 */
+bool daemonize = false; /* 是否守护进程 */
+int maxcore = 0; /* core dump文件最大容量 */
+char *username = NULL; /* 用户名 */
+char *pid_file = NULL; /* pid文件路径 */
+struct passwd *pw;
+struct sigaction sa;
+struct rlimit rlim;
+
+ + + +
捕捉SIGINT信号, 相关代码如下
+ +

main 函数接下来会设定捕捉SIGINT信号, 处理函数为sig_handler. 更多请参考 +信号处理.

+ +
/* handle SIGINT */
+signal(SIGINT, sig_handler);
+
+ +

下面是信号处理函数的定义

+ +
static void sig_handler(const int sig) {
+    printf("SIGINT handled.\n");
+    exit(EXIT_SUCCESS);
+}
+
+ +
初始化设定, 这一步是在setting_init函数中完成的, 具体来说, 设定了以下内容
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
结构体成员名称内容默认值
portTCP 端口11211
udpportUDP 端口0(不监听)
interf.s_addr监听地址INADDR_ANY
maxbytes最大使用内存64M
maxconns最大连接数1024
verbose罗嗦模式0(不罗嗦)
oldest_live最老 item0
evict_to_free使用 LRU 算法1
socketpathUNIX 域套接字地址NULL(默认不使用)
managed管理模式0
factor增长因子1.25
chunk_size数据块大小48 bytes
num_threads开启线程数4(不启用多线程为 1)
prefix_delimiter分隔符:
+ +

代码如下

+ +
/* init settings */
+settings_init();
+
+ +

下面是初始化设定函数定义以及 settings 结构体

+ +
struct settings {
+    size_t maxbytes;
+    int maxconns;
+    int port;
+    int udpport;
+    struct in_addr interf;
+    int verbose;
+    rel_time_t oldest_live; /* ignore existing items older than this */
+    bool managed;          /* if 1, a tracker manages virtual buckets */
+    int evict_to_free;
+    char *socketpath;   /* path to unix socket if using local socket */
+    double factor;          /* chunk size growth factor */
+    int chunk_size;
+    int num_threads;        /* number of libevent threads to run */
+    char prefix_delimiter;  /* character that marks a key prefix (for stats) */
+    int detail_enabled;     /* nonzero if we're collecting detailed stats */
+};
+
+static void settings_init(void) {
+    settings.port = 11211;
+    settings.udpport = 0;
+    settings.interf.s_addr = htonl(INADDR_ANY);
+    settings.maxbytes = 67108864; /* default is 64MB: (64 * 1024 * 1024) */
+    settings.maxconns = 1024;         /* to limit connections-related memory to about 5MB */
+    settings.verbose = 0;
+    settings.oldest_live = 0;
+    settings.evict_to_free = 1;       /* push old items out of cache when memory runs out */
+    settings.socketpath = NULL;       /* by default, not using a unix socket */
+    settings.managed = false;
+    settings.factor = 1.25;
+    settings.chunk_size = 48;         /* space for a modest key and value */
+#ifdef USE_THREADS
+    settings.num_threads = 4;
+#else
+    settings.num_threads = 1;
+#endif
+    settings.prefix_delimiter = ':';
+    settings.detail_enabled = 0;
+}
+
+ +
设定标准错误为非阻塞
+ +
/* set stderr non-buffering (for running under, say, daemontools) */
+setbuf(stderr, NULL);
+
+ +

关于setbuf系列函数, 参考这里

+ +
处理命令行参数
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
选项含义
UUDP 端口
bmanaged, 管理模式
pTCP 监听端口
sUnix 域套接字地址
m最大使用内存(M)
M使用 LRU 算法
c最大连接数
h帮助信息
i许可证信息
k内存锁定
v罗嗦模式, 可叠加
l监听地址(点分十进制)
ddaemonize 进程?
r最大的转储进程文件大小
u用户名(root 时需指定)
P守护进程的 pid 文件地址
f增长因子
nchunk_size 数据块大小
t最多开多少线程
D前缀分隔符
+ +

代码如下

+ +
/* process arguments */
+while ((c = getopt(argc, argv, "bp:s:U:m:Mc:khirvdl:u:P:f:s:n:t:D:")) != -1) {
+    switch (c) {
+    case 'U':
+        settings.udpport = atoi(optarg);
+        break;
+    case 'b':
+        settings.managed = true;
+        break;
+    case 'p':
+        settings.port = atoi(optarg);
+        break;
+    case 's':
+        settings.socketpath = optarg;
+        break;
+    case 'm':
+        settings.maxbytes = ((size_t)atoi(optarg)) * 1024 * 1024;
+        break;
+    case 'M':
+        settings.evict_to_free = 0;
+        break;
+    case 'c':
+        settings.maxconns = atoi(optarg);
+        break;
+    case 'h':
+        usage();
+        exit(EXIT_SUCCESS);
+    case 'i':
+        usage_license();
+        exit(EXIT_SUCCESS);
+    case 'k':
+        lock_memory = true;
+        break;
+    case 'v':
+        settings.verbose++;
+        break;
+    case 'l':
+        if (inet_pton(AF_INET, optarg, &addr) <= 0) {
+            fprintf(stderr, "Illegal address: %s\n", optarg);
+            return 1;
+        } else {
+            settings.interf = addr;
+        }
+        break;
+    case 'd':
+        daemonize = true;
+        break;
+    case 'r':
+        maxcore = 1;
+        break;
+    case 'u':
+        username = optarg;
+        break;
+    case 'P':
+        pid_file = optarg;
+        break;
+    case 'f':
+        settings.factor = atof(optarg);
+        if (settings.factor <= 1.0) {
+            fprintf(stderr, "Factor must be greater than 1\n");
+            return 1;
+        }
+        break;
+    case 'n':
+        settings.chunk_size = atoi(optarg);
+        if (settings.chunk_size == 0) {
+            fprintf(stderr, "Chunk size must be greater than 0\n");
+            return 1;
+        }
+        break;
+    case 't':
+        settings.num_threads = atoi(optarg);
+        if (settings.num_threads == 0) {
+            fprintf(stderr, "Number of threads must be greater than 0\n");
+            return 1;
+        }
+        break;
+    case 'D':
+        if (! optarg || ! optarg[0]) {
+            fprintf(stderr, "No delimiter specified\n");
+            return 1;
+        }
+        settings.prefix_delimiter = optarg[0];
+        settings.detail_enabled = 1;
+        break;
+    default:
+        fprintf(stderr, "Illegal argument \"%c\"\n", c);
+        return 1;
+    }
+}
+
+ +

这么长一段, 其实没啥东西, 不了解getopt函数的人可能会觉得摸不着头脑, +实际上很简单的, 可以参考这篇文章, 自行 Google getopt函数去吧.

+ +
配置 Core Dump 的大小 设定最大连接数
+ +

先试着取得当前转储文件大小的信息, 然后试着把它设定到最大值, 如在设定是出 +错, 就改为设定到硬限制. 然后校验是不是设定成功, 不成功便成仁这里.

+ +
if (maxcore != 0) {
+    struct rlimit rlim_new;
+    /*
+     * First try raising to infinity; if that fails, try bringing
+     * the soft limit to the hard.
+     */
+    if (getrlimit(RLIMIT_CORE, &rlim) == 0) {
+        rlim_new.rlim_cur = rlim_new.rlim_max = RLIM_INFINITY;
+        if (setrlimit(RLIMIT_CORE, &rlim_new)!= 0) {
+            /* failed. try raising just to the old max */
+            rlim_new.rlim_cur = rlim_new.rlim_max = rlim.rlim_max;
+            (void)setrlimit(RLIMIT_CORE, &rlim_new);
+        }
+    }
+    /*
+     * getrlimit again to see what we ended up with. Only fail if
+     * the soft limit ends up 0, because then no core files will be
+     * created at all.
+     */
+
+    if ((getrlimit(RLIMIT_CORE, &rlim) != 0) || rlim.rlim_cur == 0) {
+        fprintf(stderr, "failed to ensure corefile creation\n");
+        exit(EXIT_FAILURE);
+    }
+}
+
+
+ +

最大连接数其实就是可以打开的最大文件描述符 + 3(stderr, stdin, stdout). +用到的相关知识还是上面的资源设定.

+ +
/*
+ * If needed, increase rlimits to allow as many connections
+ * as needed.
+ */
+
+if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) {
+    fprintf(stderr, "failed to getrlimit number of files\n");
+    exit(EXIT_FAILURE);
+} else {
+    int maxfiles = settings.maxconns;
+    if (rlim.rlim_cur < maxfiles)
+        rlim.rlim_cur = maxfiles + 3;
+    if (rlim.rlim_max < rlim.rlim_cur)
+        rlim.rlim_max = rlim.rlim_cur;
+    if (setrlimit(RLIMIT_NOFILE, &rlim) != 0) {
+        fprintf(stderr, "failed to set rlimit for open files. Try running as root or requesting smaller maxconns value.\n");
+        exit(EXIT_FAILURE);
+    }
+}
+
+ +

本人写的关于资源方面的文章

+ +
初始化 TCP 监听套接字/初始化 UDP 套接字
+ +

这里的初始化很简单, 创建套接字, 设定一下状态. 绑定到端口, 非 UDP 套接字顺便 +监听. 之后就完了. 代码如下, 真正的初始化工作是在server_socket函数里面完 +成的, 我们会在另外一篇文章来讲述套接字初始化的相关内容.

+ +
/*
+ * initialization order: first create the listening sockets
+ * (may need root on low ports), then drop root if needed,
+ * then daemonise if needed, then init libevent (in some cases
+ * descriptors created by libevent wouldn't survive forking).
+ */
+
+/* create the listening socket and bind it */
+if (settings.socketpath == NULL) {
+    l_socket = server_socket(settings.port, 0);
+    if (l_socket == -1) {
+        fprintf(stderr, "failed to listen\n");
+        exit(EXIT_FAILURE);
+    }
+}
+
+if (settings.udpport > 0 && settings.socketpath == NULL) {
+    /* create the UDP listening socket and bind it */
+    u_socket = server_socket(settings.udpport, 1);
+    if (u_socket == -1) {
+        fprintf(stderr, "failed to listen on UDP port %d\n", settings.udpport);
+        exit(EXIT_FAILURE);
+    }
+}
+
+ +
丢掉 root 权限, 转为普通用户执行
+ +

在此之前的一些操作是需要 root 权限的, 出于安全考虑, 自此最好以普通用户权限执行.

+ +
/* lose root privileges if we have them */
+if (getuid() == 0 || geteuid() == 0) {
+    if (username == 0 || *username == '\0') {
+        fprintf(stderr, "can't run as root without the -u switch\n");
+        return 1;
+    }
+    if ((pw = getpwnam(username)) == 0) {
+        fprintf(stderr, "can't find the user %s to switch to\n", username);
+        return 1;
+    }
+    if (setgid(pw->pw_gid) < 0 || setuid(pw->pw_uid) < 0) {
+        fprintf(stderr, "failed to assume identity of user %s\n", username);
+        return 1;
+    }
+}
+
+ +
如果用到了 UNIX 域套接字, 那么在这里完成相关的初始化
+ +

这里的初始化工作在server_socket_unix函数中完成, 我们会把它和 +server_socket拿出来放到一篇文章里讲述.

+ +
/* create unix mode sockets after dropping privileges */
+if (settings.socketpath != NULL) {
+    l_socket = server_socket_unix(settings.socketpath);
+    if (l_socket == -1) {
+        fprintf(stderr, "failed to listen\n");
+        exit(EXIT_FAILURE);
+    }
+}
+
+ +
若指定以守护进程方式执行, 那么转入后台执行
+ +

daemon 函数也是很重要的一个调用, 第一个参数指定是不是改变工作目录, true 为不 +改变(这里要得到转储文件, 就不改变). 第二个参数指定是不是重定向stdin, +stdout以及stderr/dev/null. true 为不定向, 当我们处在罗嗦模式的时候, 我 +们不希望重定向操作. 关于如何把程序转到后台执行我计划单独写一篇文章来 +描述.

+ +
/* daemonize if requested */
+/* if we want to ensure our ability to dump core, don't chdir to / */
+if (daemonize) {
+    int res;
+    res = daemon(maxcore, settings.verbose);
+    if (res == -1) {
+        fprintf(stderr, "failed to daemon() in order to daemonize\n");
+        return 1;
+    }
+}
+
+ +
初始化其他内容
+ +

初始化主线程 libevent 基本事件.

+ +
/* initialize main thread libevent instance */
+main_base = event_init();
+
+ +

其他必要的初始化

+ +
/* initialize other stuff */
+item_init();
+stats_init();
+assoc_init();
+conn_init();
+slabs_init(settings.maxbytes, settings.factor);
+
+ +

初始化 item 链表

+ +
#define LARGEST_ID 255 /* 允许的最大slabclass_id, 实际上用不了这么多 */
+static item *heads[LARGEST_ID];
+static item *tails[LARGEST_ID];
+static unsigned int sizes[LARGEST_ID];
+
+void item_init(void) {
+    int i;
+    for(i = 0; i < LARGEST_ID; i++) {
+        heads[i] = NULL;
+        tails[i] = NULL;
+        sizes[i] = 0;
+    }
+}
+
+ +

初始化 stats(统计)变量

+ +
/*
+ * Stats are tracked on the basis of key prefixes. This is a simple
+ * fixed-size hash of prefixes; we run the prefixes through the same
+ * CRC function used by the cache hashtable.
+ */
+typedef struct _prefix_stats PREFIX_STATS;
+struct _prefix_stats {
+    char         *prefix;
+    size_t        prefix_len;
+    uint64_t      num_gets;
+    uint64_t      num_sets;
+    uint64_t      num_deletes;
+    uint64_t      num_hits;
+    PREFIX_STATS *next;
+};
+
+#define PREFIX_HASH_SIZE 256
+
+static PREFIX_STATS *prefix_stats[PREFIX_HASH_SIZE];
+static int num_prefixes = 0;
+static int total_prefix_size = 0;
+
+void stats_prefix_init() {
+    memset(prefix_stats, 0, sizeof(prefix_stats));
+}
+
+static void stats_init(void) {
+    stats.curr_items = stats.total_items = stats.curr_conns = stats.total_conns = stats.conn_structs = 0;
+    stats.get_cmds = stats.set_cmds = stats.get_hits = stats.get_misses = stats.evictions = 0;
+    stats.curr_bytes = stats.bytes_read = stats.bytes_written = 0;
+
+    /* make the time we started always be 2 seconds before we really
+       did, so time(0) - time.started is never zero.  if so, things
+       like 'settings.oldest_live' which act as booleans as well as
+       values are now false in boolean context... */
+    stats.started = time(0) - 2;
+    stats_prefix_init();
+}
+
+ +

初始化 key 哈希表, 确切地说, 是分配哈希桶. 哈希链表不在这里. 这里只是简单的分配 +了要使用的内存资源. 详细介绍在这里

+ +

初始化网络回收池. 当一些网络链接关闭时, 我们把它们的资源缓存在内存里, 当有新的 +连接请求进来时, 我们就不用去重新向 OS 申请资源了.

+ +
/*
+ * Free list management for connections.
+ */
+
+static conn **freeconns;
+static int freetotal;
+static int freecurr;
+
+
+static void conn_init(void) {
+    freetotal = 200;
+    freecurr = 0;
+    if (!(freeconns = (conn **)malloc(sizeof(conn *) * freetotal))) {
+        perror("malloc()");
+    }
+    return;
+}
+
+ +

初始化 slabsclass, 参考Memcached 内存管理

+ +
如果处在 managed 模式, 要分配 managed 数组空间
+ +

虽然管理模式的代码我看了好多遍, 却还是不很明白具体的作用是什么.

+ +
/* number of virtual buckets for a managed instance */
+#define MAX_BUCKETS 32768
+
+/* managed instance? alloc and zero a bucket array */
+if (settings.managed) {
+    buckets = malloc(sizeof(int) * MAX_BUCKETS);
+    if (buckets == 0) {
+        fprintf(stderr, "failed to allocate the bucket array");
+        exit(EXIT_FAILURE);
+    }
+    memset(buckets, 0, sizeof(int) * MAX_BUCKETS);
+}
+
+ +
根据设定, 决定是不是锁定内存
+ +

因为 memcached 是利用内存来实现高速缓存的, 那么, 我们就不希望在资源紧张的时候, +操作系统把我们的数据 swap 到 swap 空间去. mlockall 配上 MCL_CURRENT 和 MCL_FUTURE 保证 +现在以及将来分配的内存都不会被交换到 swap 空间. 关于这个, 也可以写一篇文章

+ +
/* lock paged memory if needed */
+if (lock_memory) {
+#ifdef HAVE_MLOCKALL
+    mlockall(MCL_CURRENT | MCL_FUTURE);
+#else
+    fprintf(stderr, "warning: mlockall() not supported on this platform.  proceeding without.\n");
+#endif
+}
+
+ +
忽略SIGPIPE信号
+ +
/*
+ * ignore SIGPIPE signals; we can use errno==EPIPE if we
+ * need that information
+ */
+sa.sa_handler = SIG_IGN;
+sa.sa_flags = 0;
+if (sigemptyset(&sa.sa_mask) == -1 ||
+    sigaction(SIGPIPE, &sa, 0) == -1) {
+    perror("failed to ignore SIGPIPE; sigaction");
+    exit(EXIT_FAILURE);
+}
+
+ +

这里为什么要忽略SIGPIPE我不清楚, 因为还没有查 APUE, 等我弄清楚了再补上来.

+ +
创建 TCP/UNIX 域新的监听连接
+ +
/* create the initial listening connection */
+if (!(listen_conn = conn_new(l_socket, conn_listening,
+                             EV_READ | EV_PERSIST, 1, false, main_base))) {
+    fprintf(stderr, "failed to create listening connection");
+    exit(EXIT_FAILURE);
+}
+
+ +

主要的工作都在conn_new函数里面完成, 那里也是各个连接到来后到达的地方. 我们会 +抽出来一篇文章单独讲解.

+ +
为守护进程写一个 pidfile.
+ +
static void save_pid(const pid_t pid, const char *pid_file) {
+    FILE *fp;
+    if (pid_file == NULL)
+        return;
+
+    if (!(fp = fopen(pid_file, "w"))) {
+        fprintf(stderr, "Could not open the pid file %s for writing\n", pid_file);
+        return;
+    }
+
+    fprintf(fp,"%ld\n", (long)pid);
+    if (fclose(fp) == -1) {
+        fprintf(stderr, "Could not close the pid file %s.\n", pid_file);
+        return;
+    }
+}
+/* save the PID in if we're a daemon */
+if (daemonize)
+    save_pid(getpid(), pid_file);
+
+ +

初始化多线程

+ +

多线程是 memcached 在 1.2.2 版本引入的新特性, 我们在 +Memcached 多线程分析有专门的探讨.

+ +

初始化程序时钟

+ +
/*
+ * We keep the current time of day in a global variable that's updated by a
+ * timer event. This saves us a bunch of time() system calls (we really only
+ * need to get the time once a second, whereas there can be tens of thousands
+ * of requests a second) and allows us to use server-start-relative timestamps
+ * rather than absolute UNIX timestamps, a space savings on systems where
+ * sizeof(time_t) > sizeof(unsigned int).
+ */
+volatile rel_time_t current_time;
+static struct event clockevent;
+
+/* time-sensitive callers can call it by hand with this, outside the normal ever-1-second timer */
+static void set_current_time(void) {
+    current_time = (rel_time_t) (time(0) - stats.started);
+}
+
+static void clock_handler(const int fd, const short which, void *arg) {
+    struct timeval t = {.tv_sec = 1, .tv_usec = 0};
+    static bool initialized = false;
+
+    if (initialized) {
+        /* only delete the event if it's actually there. */
+        evtimer_del(&clockevent);
+    } else {
+        initialized = true;
+    }
+
+    evtimer_set(&clockevent, clock_handler, 0);
+    event_base_set(main_base, &clockevent);
+    evtimer_add(&clockevent, &t);
+
+    set_current_time();
+}
+/* initialise clock event */
+clock_handler(0, 0, 0);
+
+ +

当程序并发量比较高的情况下, 利用程序时钟能显著减少对time()函数的系统调用

+ +
    +
  1. 初始化 todelete 队列
  2. +
+ +

但是, 这里并不是 LRU 算法, 这个对列和 LRU 没关系. 这里只是把那些已经过期的数据剔除 +掉罢了.

+ +
static struct event deleteevent;
+
+static void delete_handler(const int fd, const short which, void *arg) {
+    struct timeval t = {.tv_sec = 5, .tv_usec = 0};
+    static bool initialized = false;
+
+    if (initialized) {
+        /* some versions of libevent don't like deleting events that don't exist,
+           so only delete once we know this event has been added. */
+        evtimer_del(&deleteevent);
+    } else {
+        initialized = true;
+    }
+
+    evtimer_set(&deleteevent, delete_handler, 0);
+    event_base_set(main_base, &deleteevent);
+    evtimer_add(&deleteevent, &t);
+    run_deferred_deletes();
+}
+
+/* returns true if a deleted item's delete-locked-time is over, and it
+   should be removed from the namespace */
+static bool item_delete_lock_over (item *it) {
+    assert(it->it_flags & ITEM_DELETED);
+    return (current_time >= it->exptime);
+}
+
+/* Call run_deferred_deletes instead of this. */
+void do_run_deferred_deletes(void)
+{
+    int i, j = 0;
+
+    for (i = 0; i < delcurr; i++) {
+        item *it = todelete[i];
+        if (item_delete_lock_over(it)) {
+            assert(it->refcount > 0);
+            it->it_flags &= ~ITEM_DELETED;
+            do_item_unlink(it);
+            do_item_remove(it);
+        } else {
+            todelete[j++] = it;
+        }
+    }
+    delcurr = j;
+}
+
+/* initialise deletion array and timer event */
+deltotal = 200;
+delcurr = 0;
+todelete = malloc(sizeof(item *) * deltotal);
+delete_handler(0, 0, 0); /* sets up the event */
+
+ +
给每个线程的 UDP 添加可读监听
+ +
/* create the initial listening udp connection, monitored on all threads */
+if (u_socket > -1) {
+    for (c = 0; c < settings.num_threads; c++) {
+        /* this is guaranteed to hit all threads because we round-robin */
+        dispatch_conn_new(u_socket, conn_read, EV_READ | EV_PERSIST,
+                          UDP_READ_BUFFER_SIZE, 1);
+    }
+}
+
+ +
进入 libevent 事件循环, 程序正式开始服务
+ +
/* enter the loop */
+event_base_loop(main_base, 0);
+
+ +
当 libevent 事件退出, 程序结束时, 要删除曾经的 pidfile
+ +

一只感觉这里执行不到啊…

+ +
static void remove_pidfile(const char *pid_file) {
+    if (!pid_file)
+        return;
+
+    if (unlink(pid_file) != 0) {
+        fprintf(stderr, "Could not remove the pid file %s.\n", pid_file);
+    }
+
+}
+/* remove the PID file if we're a daemon */
+if (daemonize)
+    remove_pidfile(pid_file);
+
+
+
+ +
+ + +
+
+ +
+ + +
+
+ + +
+
+ +
+
+ +
+ + + + + + +
+ + + + diff --git a/2015/10/17/craftyjs-getting-start.html b/2015/10/17/craftyjs-getting-start.html new file mode 100644 index 0000000..77605a1 --- /dev/null +++ b/2015/10/17/craftyjs-getting-start.html @@ -0,0 +1,1313 @@ + + + +Getting started - 创建你的第一个游戏 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Getting started - 创建你的第一个游戏

  + +
+
+ + +

这是一个使用 Crafty 的快速指南.

+ +

这里有一个 Darren Torpey 写的深入但是过时的 +教程. (对 0.6.3 版本, +blabla… As of 0.6.3, in addition to the changes Darren mentions at the end, you’ll need to change the signature of Crafty.load)

+ + + +

Setup

+ +

我们先从 HTML 文件开始. 我们试着让你能快速运行程序, 这里偷个懒, 直接连接官网最新 +发布版本. 当然了, 你也可以本地安装它.

+ +
<html>
+  <head></head>
+  <body>
+    <div id="game"></div>
+    <script
+      type="text/javascript"
+      src="https://rawgithub.com/craftyjs/Crafty/release/dist/crafty-min.js"
+    ></script>
+    <script>
+      Crafty.init(500, 350, document.getElementById("game"));
+    </script>
+  </body>
+</html>
+
+ +

Crafty.js 游戏是由实体(entities)组成的, 角色, 敌人以及障碍物等等都可以用实体来 +表示.

+ +

下面我们来创建一个简单的色块.

+ +
Crafty.e("2D, DOM, Color").attr({ x: 0, y: 0, w: 100, h: 100 }).color("#F00");
+
+ +

这里涉及到几点:

+ +
    +
  • +

    首先调用Crafty.e, 并传递一系列组件附加到这个实体上. +组件提供了基本的功能函数. 在这个例子里, 我们添加了2D, +DOM以及 Color. 下面将会对这些进行更多介绍

    +
  • +
  • +

    我们调用了新建实体的两个方法: attr() 以及 color(). attr方法是 +几个所有实体都会共有的方法, 但是color是由Color组件 +提供的, 这个实体的多数方法都会返回实体本身, 这允许我们像例子中一样执行链式操作.

    +
  • +
+ +

完整的代码看起来应该像这样:

+ +
<html>
+  <head></head>
+  <body>
+    <div id="game"></div>
+    <script
+      type="text/javascript"
+      src="https://rawgithub.com/craftyjs/Crafty/release/dist/crafty-min.js"
+    ></script>
+    <script>
+      Crafty.init(500, 350, document.getElementById("game"));
+      Crafty.e("2D, DOM, Color")
+        .attr({ x: 0, y: 0, w: 100, h: 100 })
+        .color("#F00");
+    </script>
+  </body>
+</html>
+
+ +

执行结果如下:

+ + + +

现在我们的屏幕上会显示一个红色色块. 下面让我们来试着用键盘上的方向键移动它.

+ +

我们用“Fourway”组件完成这个需求.

+ +
Crafty.e("2D, DOM, Color, Fourway")
+  .attr({ x: 0, y: 0, w: 100, h: 100 })
+  .color("#F00")
+  .fourway(4);
+
+ +

请注意我们是如何在实体的 Color 组件后面添加组件名称的.这里添加了一个.fourway +方法, 传递给它的参数可以决定移动速度, 也即, 数字越大, 移动的越快.

+ + + +

下面我们把它修正的像平台游戏一样使色块可以有重力. 可以通过Grivaity组件完成.

+ +

但是若我们直接添加重力组件, 实体将会直接掉落, 因为没有东西能够阻止它掉落. 所以 +添加一个绿长条将会提供一个阻止下落的界面.

+ +
Crafty.e("Floor, 2D, Canvas, Color")
+  .attr({ x: 0, y: 250, w: 250, h: 10 })
+  .color("green");
+
+ +

请注意我们是如何向这个实体添加Floor组件的. 你在 API 文档里面找不到这个组件, 它 +也不提供任何新的方法. 它只是我们起的一个名字, 可以用来标识这个组件.

+ +

重力组件应该只用于那些应该掉落的实体上. 所以新的实体并不需要添加重力模块.

+ +

现在, 我们把重力模块应用到之前的红色色块上去.

+ +
Crafty.e("2D, Canvas, Color, Fourway, Gravity")
+  .attr({ x: 0, y: 0, w: 50, h: 50 })
+  .color("#F00")
+  .fourway(4)
+  .gravity("Floor");
+
+ +

你应该注意到了, .gravity()方法在调用时传递了”Floor”参数. 这意味着, 所有有 +Floor 组件的实体都会阻止这个实体掉落.

+ + + +

额, 这不像是一个好游戏, 但是这已经算是开始了. 要学习如何更多的使用 Crafty, 你可 +以浏览overview, 或者详细的api documentation.

+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/10/31/vim-as-a-php-ide.html b/2015/10/31/vim-as-a-php-ide.html new file mode 100644 index 0000000..f3bda44 --- /dev/null +++ b/2015/10/31/vim-as-a-php-ide.html @@ -0,0 +1,1238 @@ + + + +Vim as a PHP IDE - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Vim as a PHP IDE

  + +
+
+ + +

这篇文章内容基本上是一个老外写的, 我读完了感觉不错, 遂自己做了点笔记. 附上 +原文链接

+ +

代码风格检查

+ +

这里用到了 PHP CodeSniffer, 可以从 PEAR 安装它, 命令如下

+ +
pear install PHP_CodeSniffer
+
+ + + +

之后我们配置 VIM 支持此项检查, 相关的代码在 Github 上有, 附上 +链接

+ +

ManPageViewer 快速查看 Man Pages

+ +

这个插件, 用来快速查看 man pages. 从 +这里下载 +之后用 vim 打开, 然后执行:so %就可以安装了.

+ +

安装完成后, 打开文件, 找到你想查看的函数, 按住Shift + K, 就可以查看页面了.

+ +

这里要注意, PHP 默认是要到http://php.net获得在线页面的, +实际上 PHP 有一样好东西, 可以让我们像查看 C 语言函数那样查看 PHP 手册, 那就是 +php man pages. 我们可以从 PHP 官方下载

+ +
pear install doc.php.net/pman
+
+ +

之后要修改 ManPageViewer, 100 行左右, PHP 相关的代码大约有 10 几行, 删掉, 换成

+ +
if !exists("g:manpageview_pgm_php") && (executable("pman"))
+    "Decho "installed php help support via manpageview"
+    let g:manpageview_pgm_php     = "pman"
+endif
+
+ +

xdebug 调试工具

+ +

原文有这部分内容, 但是我不怎么用 xdebug. 故此处省略一万字.

+ +

其他

+ +

下面是几个推荐的插件, 安装教程比较多, 可以自行搜索

+ +
    +
  • autocompletition 自动补全工具
  • +
  • phpDocumentor for vim 注释工具
  • +
  • cscope 检索
  • +
  • taglist 检索
  • +
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/11/04/Memcached-socket-init.html b/2015/11/04/Memcached-socket-init.html new file mode 100644 index 0000000..1171e35 --- /dev/null +++ b/2015/11/04/Memcached-socket-init.html @@ -0,0 +1,1408 @@ + + + +Memcached套接字的初始化 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Memcached套接字的初始化

  + +
+
+ + +

之所以把套接字初始化从Memcached 的初始化过程拿出来, 主要考虑 +到那文章已经很长了, 再者, Memcached 一共用到了 3 种套接字(即: TCP, UDP 和 NUIX 域套 +接字), 单独拿出来说, 顺便做一个对比, 更能深入地帮助我们理解这三种套接字之间的相 +同点与不同点.

+ + + +

TCP/UDP 套接字的初始化

+ +

废话不多说, 先回忆下, TCP 和 UDP 在调用初始化函数时的差异

+ +
/* listening socket */
+static int l_socket = 0;
+/* udp socket */
+static int u_socket = -1;
+
+/* TCP */
+l_socket = server_socket(settings.port, 0);
+/* UDP */
+u_socket = server_socket(settings.udpport, 1);
+
+ +

这里有很有意思的一点, udpport, 我们知道, UDP 是不需要监听的, 但是他像 TCP 一样, 也 +需要一个端口用来通讯.

+ +

接下来上初始化函数代码欣赏下

+ +
static int server_socket(const int port, const bool is_udp) {
+    /* 即将返回的socket描述符 */
+    int sfd;
+    struct linger ling = {0, 0};
+    struct sockaddr_in addr;
+    int flags =1;
+
+    /* 哈哈, 其实server_socket函数也是个架子, 脏活类活是new_socket在干 */
+    if ((sfd = new_socket(is_udp)) == -1) {
+        return -1;
+    }
+
+    /* 设置地址重用 */
+    setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (void *)&flags, sizeof(flags));
+    if (is_udp) {
+        /* UDP 的发送缓存区设定到系统允许的最大值 */
+        maximize_sndbuf(sfd);
+    } else {
+        /* 还是套接字特性设定, 自行查资料 */
+        setsockopt(sfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags, sizeof(flags));
+        setsockopt(sfd, SOL_SOCKET, SO_LINGER, (void *)&ling, sizeof(ling));
+        setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, (void *)&flags, sizeof(flags));
+    }
+
+    /*
+     * the memset call clears nonstandard fields in some impementations
+     * that otherwise mess things up.
+     */
+    memset(&addr, 0, sizeof(addr));
+
+    addr.sin_family = AF_INET;
+    addr.sin_port = htons(port);
+    addr.sin_addr = settings.interf;
+    /* 无论是TCP还是UDP都是需要绑定的, 看看绑定的元素: 地址(IP),
+     * 端口(port)还有协议簇
+     */
+    if (bind(sfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
+        perror("bind()");
+        close(sfd);
+        return -1;
+    }
+    /* 非UDP套接字才会bind操作, 还记得么, &&运算符的短路特性 */
+    if (!is_udp && listen(sfd, 1024) == -1) {
+        perror("listen()");
+        close(sfd);
+        return -1;
+    }
+    return sfd;
+}
+
+static int new_socket(const bool is_udp) {
+    int sfd;
+    int flags;
+
+    /* 现在才真正的创建了一个套接字 */
+    if ((sfd = socket(AF_INET, is_udp ? SOCK_DGRAM : SOCK_STREAM, 0)) == -1) {
+        perror("socket()");
+        return -1;
+    }
+
+    /* 现在我们操作文件描述符的特性, 设定这个套接字不阻塞 */
+    if ((flags = fcntl(sfd, F_GETFL, 0)) < 0 ||
+        fcntl(sfd, F_SETFL, flags | O_NONBLOCK) < 0) {
+        perror("setting O_NONBLOCK");
+        close(sfd);
+        return -1;
+    }
+    return sfd;
+}
+
+#define MAX_SENDBUF_SIZE (256 * 1024 * 1024)
+/*
+ * Sets a socket's send buffer size to the maximum allowed by the system.
+ */
+static void maximize_sndbuf(const int sfd) {
+    socklen_t intsize = sizeof(int);
+    int last_good = 0;
+    int min, max, avg;
+    int old_size;
+
+    /* Start with the default size. */
+    if (getsockopt(sfd, SOL_SOCKET, SO_SNDBUF, &old_size, &intsize) != 0) {
+        if (settings.verbose > 0)
+            perror("getsockopt(SO_SNDBUF)");
+        return;
+    }
+
+    /* Binary-search for the real maximum. */
+    /* 这里, 我们并不知道系统允许我们设定到多大, 那么, 用二分法试探吧 */
+    min = old_size;
+    max = MAX_SENDBUF_SIZE;
+
+    while (min <= max) {
+        avg = ((unsigned int)(min + max)) / 2;
+        if (setsockopt(sfd, SOL_SOCKET, SO_SNDBUF, (void *)&avg, intsize) == 0) {
+            last_good = avg;
+            min = avg + 1;
+        } else {
+            max = avg - 1;
+        }
+    }
+
+    if (settings.verbose > 1)
+        fprintf(stderr, "<%d send buffer was %d, now %d\n", sfd, old_size, last_good);
+}
+
+ +

参考资料: fcntl 操作文件描述符特性

+ +

UNIX domain socket

+ +

下面再来看以下 UNIX 域套接字的初始化.

+ +
static int new_socket_unix(void) {
+    int sfd;
+    int flags;
+
+    /* 注意这里的地址簇, 不再是AF_INET, 二是AF_UNIX */
+    if ((sfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+        perror("socket()");
+        return -1;
+    }
+
+    /* 这里是和TCP/UDP一样的, 设定为非阻塞 */
+    if ((flags = fcntl(sfd, F_GETFL, 0)) < 0 ||
+        fcntl(sfd, F_SETFL, flags | O_NONBLOCK) < 0) {
+        perror("setting O_NONBLOCK");
+        close(sfd);
+        return -1;
+    }
+    return sfd;
+}
+
+static int server_socket_unix(const char *path) {
+    int sfd;
+    struct linger ling = {0, 0};
+    struct sockaddr_un addr;
+    struct stat tstat;
+    int flags =1;
+
+    if (!path) {
+        return -1;
+    }
+
+    /* 先申请一个套接字文件描述符 */
+    if ((sfd = new_socket_unix()) == -1) {
+        return -1;
+    }
+
+    /*
+     * Clean up a previous socket file if we left it around
+     * 之前有老的套接字文件, 要干掉它, 待会将要创建新的
+     */
+    if (lstat(path, &tstat) == 0) {
+        if (S_ISSOCK(tstat.st_mode))
+            unlink(path);
+    }
+
+    /* 套接字特性设定 */
+    setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (void *)&flags, sizeof(flags));
+    setsockopt(sfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags, sizeof(flags));
+    setsockopt(sfd, SOL_SOCKET, SO_LINGER, (void *)&ling, sizeof(ling));
+
+    /*
+     * the memset call clears nonstandard fields in some impementations
+     * that otherwise mess things up.
+     */
+    memset(&addr, 0, sizeof(addr));
+
+    /* 协议簇 */
+    addr.sun_family = AF_UNIX;
+    /* socket文件地址  */
+    strcpy(addr.sun_path, path);
+    /* 绑定到sfd(socket文件描述符)到socket文件 */
+    if (bind(sfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
+        perror("bind()");
+        close(sfd);
+        return -1;
+    }
+    /* 监听, 同TCP一样, 要监听 */
+    if (listen(sfd, 1024) == -1) {
+        perror("listen()");
+        close(sfd);
+        return -1;
+    }
+    return sfd;
+}
+
+ +

至此, TCP/UDP/UNIX 域套接字的初始化工作已经完成, 此时, 他们都处在等待客户连接的 +状态, 虽然我们还没有 accept, 但是此时雏形已经形成. 之后的读操作由 libevent 通知我 +们, 当这些套接字上可读时, 就意味着有新的链接请求过来了.

+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/11/05/Memcached-slabs.html b/2015/11/05/Memcached-slabs.html new file mode 100644 index 0000000..fa30c03 --- /dev/null +++ b/2015/11/05/Memcached-slabs.html @@ -0,0 +1,1724 @@ + + + +Memcached的slabs(内存管理) - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Memcached的slabs(内存管理)

  + +
+
+ + +

初始化内存管理部分. +Memcached 是按照页面来管理它使用的内存的. 这样做的好处是可以减少每次都新申请内 +存的malloc调用, 但是不可避免的产生了内存空间浪费. 本篇分析 Memcached 的内存管 +理机制

+ + + +

初始化部分分析

+ +

我们先来看内存管理用到的数据结构, 理解了这一块会对我们理解代码有很大的帮助.

+ +
#define POWER_SMALLEST 1
+#define POWER_LARGEST  200
+#define POWER_BLOCK 1048576
+#define CHUNK_ALIGN_BYTES (sizeof(void *))
+#define DONT_PREALLOC_SLABS
+
+ +

上面的定义中, POWER_XX都是和内存块有关的. POWER_SMALLEST表示, 内存块的最小 +是 2^1 (即POWER_SMALLEST), 也即 2 Bytes. 最大是 2^200 (即POWER_LARGEST), 这是一个 +很大的数了, 实际上更本用不到这么多. 题外话, 这两个值在最开始(03-06 年)实际上分别是 6 +和 20, 也就是 2^6 = 48 Bytes 和 2^20 = 1M, 48Bytes 是 memcached 默认的最小 chunk 大小, +1M 恰好是 Memcached 允许存储的单个元素最大值(算上 key, exptime 等等). +POWER_BLOCK是一个 slab, 也即一个页面的大小, 我们有时候把它叫做 slab, 有时候把它叫 +做 page. 实际上指的是一个东西.

+ +

CHUNK_ALIGN_BYTES 指定了内存对齐相关的内容. 对齐的内存有更快的存取速度和更好 +的移植性. 而DONT_PREALLOC_SLABS的定义则把prealloc_slabs功能禁用. 这个功能对 +那些不了解 memcached 机制的人来说, 可能会更友好些, 但是此功能是不必要的.

+ +

接下来是 slabclass 的数据结构定义. slabclass 是组织页面的数据结构, 每个 +slalclass 里面可以有多个 chunk size 一样的 slab, 而每个 slab 再进一步划分为相应大小 +的 chunk.

+ +
/* powers-of-N allocation structures */
+
+typedef struct {
+    unsigned int size;      /* sizes of items */
+    unsigned int perslab;   /* how many items per slab */
+
+    void **slots;           /* list of item ptrs */
+    unsigned int sl_total;  /* size of previous array */
+    unsigned int sl_curr;   /* first free slot */
+
+    void *end_page_ptr;         /* pointer to next free item at end of page, or 0 */
+    unsigned int end_page_free; /* number of items remaining at end of last alloced page */
+
+    unsigned int slabs;     /* how many slabs were allocated for this class */
+
+    void **slab_list;       /* array of slab pointers */
+    unsigned int list_size; /* size of prev array */
+
+    unsigned int killing;  /* index+1 of dying slab, or zero if none */
+} slabclass_t;
+
+ +

我们来解释一下, 各个成员变量.

+ +

第一个, size, 顾名思义, 就是说这个 slabclass 有多少个 item 的. preslab是说, 在 +每个 slab 有多少个 chunk(能存多少个 item).

+ +

接下来的成员叫做slots, 是个指向指针的指针, 这个成员很重要, 它记录了那些 slab 里 +面已经 free 的闲置空间. 如果没有这个成员, 我们就很难做到 LRU 了. sl_total标识了有 +多少空闲的*slots位置可供使用, 防止我们在遍历slots时越界, 也提醒我们在 +slots不够用的时候分配新的空间, 而sl_curr则说明了当前第一个空闲可用 chunk 的偏 +移(相对于*slot).

+ +

end_page_ptr, 这个也很重要, 这个成员指向了 slabclass 最新分配的那个 slab, +end_page_free是一个游标, 指向了最新 slab 里面第一个可用的 chunk, 当这个值增长到 +preslab是, 说明这个 chunk 已经用完了, 在后面的代码里我们会看到, 我们是优先使用 +这个 slots 里面的空闲 chunk 的. 所以, 当这里也用完了, 就意味着我们需要分配新的空间 +了(当然了, 代码里没有立即申请, 因为在下一次存储请求到来之前, 说不定那些数据就 +过期了呢…)

+ +

slabs记录了这个 slabclass 当前有多少 slab, 接下来的二维指针用于记录这个 +slabclass 的各个 slab, list_size是指针数组的大小, slab_list用到的空间是通过 +2^N 来分配的, 这个值应该大于等于slabs. 最下面的 killing 作用不甚清除, 我随时 +补充

+ +

补充 killing 指的是要 reassign 那个 slab, 这个成员函数好象就在这里用到了

+ +

在往后是几个文件作用域变量, 简单看一下. 需要注意, slabclass 的长度是 +POWER_LARGEST + 1

+ +
static slabclass_t slabclass[POWER_LARGEST + 1];
+static size_t mem_limit = 0;
+static size_t mem_malloced = 0;
+static int power_largest;
+
+ +

下面是两个函数原型, 根据编译条件, 可以设定是不是编译slabs_preallocate函数.

+ +
/*
+ * Forward Declarations
+ */
+static int do_slabs_newslab(const unsigned int id);
+
+#ifndef DONT_PREALLOC_SLABS
+/* Preallocate as many slab pages as possible (called from slabs_init)
+   on start-up, so users don't get confused out-of-memory errors when
+   they do have free (in-slab) space, but no space to make new slabs.
+   if maxslabs is 18 (POWER_LARGEST - POWER_SMALLEST + 1), then all
+   slab types can be made.  if max memory is less than 18 MB, only the
+   smaller ones will be made.  */
+static void slabs_preallocate (const unsigned int maxslabs);
+#endif
+
+ +

好了, 数据结构已经了解的差不多了, 下面来看看初始化代码(main函数里面, +调用了slabs_init, 还记得么?)

+ +
/*
+ * Determines the chunk sizes and initializes the slab class descriptors
+ * accordingly.
+ */
+void slabs_init(const size_t limit, const double factor) {
+    int i = POWER_SMALLEST - 1;
+    unsigned int size = sizeof(item) + settings.chunk_size;
+
+    /* Factor of 2.0 means use the default memcached behavior */
+    if (factor == 2.0 && size < 128)
+        size = 128;
+
+    mem_limit = limit;
+    memset(slabclass, 0, sizeof(slabclass));
+
+    while (++i < POWER_LARGEST && size <= POWER_BLOCK / 2) {
+        /* Make sure items are always n-byte aligned 内存对齐 */
+        if (size % CHUNK_ALIGN_BYTES)
+            size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);
+
+        /* 每个数据块的大小 */
+        slabclass[i].size = size;
+        /* 计算得到, 每页(1M)能存放多少chunk */
+        slabclass[i].perslab = POWER_BLOCK / slabclass[i].size;
+        /* 计算得到下一个slabclass里面每个chunk的大小 */
+        size *= factor;
+
+        if (settings.verbose > 1) {
+            fprintf(stderr, "slab class %3d: chunk size %6u perslab %5u\n",
+                    i, slabclass[i].size, slabclass[i].perslab);
+        }
+    }
+
+    /* 最后一个slabclass单独处理, 每个slab可以存放一个item */
+    power_largest = i;
+    slabclass[power_largest].size = POWER_BLOCK;
+    slabclass[power_largest].perslab = 1;
+
+    /* 下面一点代码是为了测试方便, 手动在环境中设定了一个值,
+       模拟已经分配的内存
+       for the test suite:  faking of how much we've already malloc'd */
+    {
+        char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
+        if (t_initial_malloc) {
+            mem_malloced = (size_t)atol(t_initial_malloc);
+        }
+
+    }
+
+#ifndef DONT_PREALLOC_SLABS
+    /* 给每个slabclass都预分配一个slab, 用户比较眯瞪... */
+    {
+        /* 测试变量, 模拟预分配设定大小 */
+        char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");
+
+        if (pre_alloc == NULL || atoi(pre_alloc) != 0) {
+            slabs_preallocate(power_largest);
+        }
+    }
+#endif
+}
+
+#ifndef DONT_PREALLOC_SLABS
+static void slabs_preallocate (const unsigned int maxslabs) {
+    int i;
+    unsigned int prealloc = 0;
+
+    /* pre-allocate a 1MB slab in every size class so people don't get
+       confused by non-intuitive "SERVER_ERROR out of memory"
+       messages.  this is the most common question on the mailing
+       list.  if you really don't want this, you can rebuild without
+       these three lines.  */
+
+    /* 循环给每个slabclass分配一个slab */
+    for (i = POWER_SMALLEST; i <= POWER_LARGEST; i++) {
+        if (++prealloc > maxslabs)
+            return;
+        /* 这里给slabclass_i分配一个页面(page或者叫slab, 大小为1M) */
+        do_slabs_newslab(i);
+    }
+
+}
+#endif
+
+ +

上面说了很多, 但是逻辑很简单, 首先初始化 slabclass 空间. 之后根据编译条件, 选择是 +不是预分配空间给各个 slabclass. 在slabs_preallocate函数中, 我们调用了一个叫做 +do_slabs_newslab的函数, 这个函数负责分配一个 slab 的内存空间, 在程序的新分配页面 +的时候也调用了这个函数. 再看这个函数的工作原理之前, 我们在夯实一下基础, 看看 +追踪 slab 的slab_list数组的分配.

+ +
static int grow_slab_list (const unsigned int id) {
+    /* 这里操作slabclass[i]的引用 */
+    slabclass_t *p = &slabclass[id];
+    /* 当slabs的数目达到了分配的空间大小, 就要考虑分配新的空间了 */
+    if (p->slabs == p->list_size) {
+        /* 初始化的大小是16个, 之后才会成2倍增长 */
+        size_t new_size =  (p->list_size != 0) ? p->list_size * 2 : 16;
+        /* slab_list里面存放的是各个slab的指针, 我们分配的单位是指针大小 */
+        void *new_list = realloc(p->slab_list, new_size * sizeof(void *));
+        if (new_list == 0) return 0;
+        /* 更新数组大小并指向新的空间 */
+        p->list_size = new_size;
+        p->slab_list = new_list;
+    }
+    return 1;
+}
+
+ +

我们准备的够充分了, 下面来看看do_slabs_newslab函数是怎么工作的.

+ +
static int do_slabs_newslab(const unsigned int id) {
+    /* 这里, 我们要操作的是slabclass[id]的引用 */
+    slabclass_t *p = &slabclass[id];
+
+/* 这个条件是指示是不是允许slab在不同的slabclass之间移动的
+ * 允许移动的, 在下一个slabclass里面可能会使用比现有len多的空间, 所以要设定成
+ * 为POWER_BLOCK, 而不允许分配, 那我们就要尽可能的节约内存, 能省多少是多少.
+ */
+#ifdef ALLOW_SLABS_REASSIGN
+    int len = POWER_BLOCK;
+#else
+    int len = p->size * p->perslab;
+#endif
+    char *ptr;
+
+    /* 检查内存使用情况, 确保不会超出设定的使用范围
+     * p->slabs标识了当前slabcalss有多少页面, 这里为何要大于0不是很清楚,
+     * 我随时补充(#TDOD)
+     */
+    if (mem_limit && mem_malloced + len > mem_limit && p->slabs > 0)
+        return 0;
+
+    if (grow_slab_list(id) == 0) return 0;
+
+    /* 给slab分配空间 */
+    ptr = malloc((size_t)len);
+    if (ptr == 0) return 0;
+
+    /* 把新分配的页面(slab)挂到end_page_ptr, 并设定end_page_free指向可用chunk */
+    memset(ptr, 0, (size_t)len);
+    p->end_page_ptr = ptr;
+    p->end_page_free = p->perslab;
+
+    /* 这个页面还应该挂到slab_list, 方便以后管理它 */
+    p->slab_list[p->slabs++] = ptr;
+    mem_malloced += len;
+    return 1;
+}
+
+ +

slab item 的分配算法

+ +

上面介绍了 slabclass 的初始化过程, 在运行中, 当 slabclass 上的 slots 里面没有可用空 +间的时候, 就会向系统申请新的页面, 这个过程就是 memcached 内存管理比较核心的东西 +了. 接下来是相关的代码.

+ +

这里插入一点基础内容, 那就是, 我们知道数据是根据大小定位到相应的 slabclass, +然后在这个 slabclass 的 slabs 里面找一块空间来存储数据. 那么, memcached 是怎么确定 +应该把数据存放到那个 slabclass 里面呢?

+ +
/*
+ * Figures out which slab class (chunk size) is required to store an item of
+ * a given size.
+ *
+ * Given object size, return id to use when allocating/freeing memory for object
+ * 0 means error: can't store such a large object
+ */
+
+unsigned int slabs_clsid(const size_t size) {
+    int res = POWER_SMALLEST;
+
+    if (size == 0)
+        return 0;
+    /* 找到那个最小但是又能装下内容的slabclass */
+    while (size > slabclass[res].size)
+        if (res++ == power_largest)     /* won't fit in the biggest slab */
+            return 0;
+    return res;
+}
+
+ +

事实上, 函数里在分配 item 空间时, 调用的是一个叫做slabs_alloc的函数, 它实际上 +是一个宏, 根据编译选项的不同, 可能是do_slabs_alloc或者mt_slabs_alloc. 这 +个宏定义在 memcached.h 中, 相关代码摘抄如下(这里定义了巨量的多线程加锁版本函数, +我们只看涉及到的, 其他的以后再说).

+ +
#ifdef USE_THREADS
+void *mt_slabs_alloc(size_t size);
+
+# define slabs_alloc(x)              mt_slabs_alloc(x)
+#else /* !USE_THREADS 这个ifdef条件太长了, 以至于要加上注释方便知道这个else是谁的 */
+# define slabs_alloc(x)              do_slabs_alloc(x)
+#endif /* !USE_THREADS */
+
+ +

OK, 由于mt_slabs_alloc只是do_slabs_alloc的加锁版本, 那么我们先来看 +do_slabs_alloc.

+ +
/*@null@*/
+void *do_slabs_alloc(const size_t size) {
+    slabclass_t *p;
+
+    /* 找ID? 我们已经知道你是怎么找的啦. */
+    unsigned int id = slabs_clsid(size);
+    if (id < POWER_SMALLEST || id > power_largest)
+        return NULL;
+
+    /* 定位到相应的slabclass, 待会从里面取得item存储空间 */
+    p = &slabclass[id];
+    /* 下面这个断言很有意思, 揭示了新的item分配的一些内部状态, 来解释一下.
+     * p->sl_curr == 0 表明, 这个新slabclass的第一个空闲item槽(slot)为空,
+     * 即初始化的状态. 因为只有在这种状态下, 我们才会考虑使用`end_page_ptr`
+     * 指向的空闲item. 然后如果在这里发现空间不够, 需要申请新的slab. 否则,
+     * 我们就不需要去申请新的slab. 当该值不为零, 我们就需要验证第二个条件,
+     * 这个状态涉及到一些我们在删除item的时候的一些操作, 在删除时, 我们有设定
+     * slabs_clsid = 0的操作. 这里拿来验证一下以确保我们得到的确实是释放了的
+     * 空闲chunk
+     */
+    assert(p->sl_curr == 0 || ((item *)p->slots[p->sl_curr - 1])->slabs_clsid == 0);
+
+/* USE_SYSTEM_MALLOC 定义是不是使用我们自己的这一套slab内存管理系统
+ * 若不使用, 就直接调用系统接口, 这种方式的效率肯定比不上slab, 但是优点就是简单
+ */
+#ifdef USE_SYSTEM_MALLOC
+    if (mem_limit && mem_malloced + size > mem_limit)
+        return 0;
+    mem_malloced += size;
+    return malloc(size);
+#endif
+
+    /* fail unless we have space at the end of a recently allocated page,
+       we have something on our freelist, or we could allocate a new page
+       检查两个地方, 即sl_curr和end_page_ptr, 当着两个都为0, 我们就需要
+       申请新的slab了, sl_curr == 0表示没有空闲item好理解. end_page_ptr
+       在哪设置? 答案是本函数后面几行, 请仔细看.
+
+       do_slabs_alloc会帮我们正确的设置end_page_ptr以及end_page_free,
+       后面我们就可以高枕无忧的获取新item内存了
+     */
+    if (! (p->end_page_ptr != 0 || p->sl_curr != 0 || do_slabs_newslab(id) != 0))
+        return 0;
+
+    /* return off our freelist, if we have one, 这里是从slots反会, 优先使用 */
+    if (p->sl_curr != 0)
+        return p->slots[--p->sl_curr];
+
+    /* if we recently allocated a whole page, return from that
+       不行就从end_page_ptr(最后一次分配的那个页面)返回
+     */
+    if (p->end_page_ptr) {
+        void *ptr = p->end_page_ptr;
+        /* end_page_ptr增长一个item大小, end_page_free 减去1 */
+        if (--p->end_page_free != 0) {
+            p->end_page_ptr += p->size;
+        } else {
+            p->end_page_ptr = 0;
+        }
+        return ptr;
+    }
+
+    /* 走到这? 去死吧, 肯定哪里出错了 */
+    return NULL;  /* shouldn't ever get here */
+}
+
+ +

slab item 的回收算法

+ +

有分配, 对应的也会有回收.现在我们来了解下实现的细节. 此函数接受两个参数, +分别是 item 的 pointer 和 size. 这个函数只是实现了内存回收, 具体的 item 作废, 是在 +item 管理模块完成的. 我们在其他文章做详细介绍

+ +
void do_slabs_free(void *ptr, const size_t size) {
+    unsigned char id = slabs_clsid(size);
+    slabclass_t *p;
+
+    /* 本函数被调用之前, 已经准备好了将要释放这个item,
+       那时候它的slabs_clsid即已经为0了 */
+    assert(((item *)ptr)->slabs_clsid == 0);
+    /* 断言ID应该在一个合理的范围 */
+    assert(id >= POWER_SMALLEST && id <= power_largest);
+    if (id < POWER_SMALLEST || id > power_largest)
+        return;
+
+    /* 老规矩, 操作引用. */
+    p = &slabclass[id];
+
+/* 使用系统调用管理内存 */
+#ifdef USE_SYSTEM_MALLOC
+    mem_malloced -= size;
+    free(ptr);
+    return;
+#endif
+
+    /* slots槽满了, 新分配一点槽, 用来装更多的空闲chunk */
+    if (p->sl_curr == p->sl_total) { /* need more space on the free list */
+        int new_size = (p->sl_total != 0) ? p->sl_total * 2 : 16;  /* 16 is arbitrary */
+        void **new_slots = realloc(p->slots, new_size * sizeof(void *));
+        if (new_slots == 0)
+            return;
+        p->slots = new_slots;
+        p->sl_total = new_size;
+    }
+    /* 闲置chunk放到槽里 */
+    p->slots[p->sl_curr++] = ptr;
+    return;
+}
+
+ +

至此, slabs 模块已经介绍了个大概了, 还剩下统计和 reassign 两个功能没有介绍. 统计 +我们打算到介绍 Memcached 的统计功能时再介绍. 下面来看看 reassign. 源码注释里提到, +这个功能默认是关闭的, 应为它可能会造成内存的浪费, 但是这个方法实现了手动管理 +内存的机制, 权衡之下, 这个功能还是可以说是利器. 不过可能并不好用, 因为迁移 slab +要满足源 slab 的新 slab 指针指向空并且要有 slab. 目标 slab 要满足新 slab 指针指向空, 还 +要有空间来容纳这个迁过来的 slab. 仅仅是指向空这些条件, 在整体内存没有达到 +settings.maxbytes 或者内存没有耗干之前, 还是比较难以控制的. 当然, 在达到内存上 +限之后, 这些条件就一定会满足了. 到那个时候使用 slab reassign 就好多了

+ +

+
+ +

下面是实现 reassign 的代码, 利用这段代码, 可以实现手动管理内存的需求.

+ +
#ifdef ALLOW_SLABS_REASSIGN
+/* Blows away all the items in a slab class and moves its slabs to another
+ * class. This is only used by the "slabs reassign" command, for manual tweaking
+ * of memory allocation. It's disabled by default since it requires that all
+ * slabs be the same size (which can waste space for chunk size mantissas(尾数) of
+ * other than 2.0).
+ * 1 = success
+ * 0 = fail
+ * -1 = tried. busy. send again shortly.
+ *
+ * 这里说, 需要大小一样, 就是指len = POWER_LARGEST or (size * perslab)
+ */
+int do_slabs_reassign(unsigned char srcid, unsigned char dstid) {
+    void *slab, *slab_end;
+    slabclass_t *p, *dp;
+    void *iter;
+    bool was_busy = false;
+
+    /* 先判断数据是不是明显不符合条件 */
+    if (srcid < POWER_SMALLEST || srcid > power_largest ||
+        dstid < POWER_SMALLEST || dstid > power_largest)
+        return 0;
+
+    /* 操作引用 */
+    p = &slabclass[srcid];
+    dp = &slabclass[dstid];
+
+    /* fail if src still populating, or no slab to give up in src
+     * 能迁移的前提是, 本slabclass没有空闲的end_page, 并且它包含的
+     * items个数不能为0, 也就是说, 这个slabclass不能为空
+     * 简单说, 就是
+     *
+     * if (p->end_page_ptr == 0 && p->slabs != 0)
+     */
+    if (p->end_page_ptr || ! p->slabs)
+        return 0;
+
+    /* fail if dst is still growing or we can't make room to hold its new one
+     * 道理和src slabcalss一样的, 但是增加了slab_list和list_size的检查, 确保
+     * 有空间接受新来的这个slab
+     */
+    if (dp->end_page_ptr || ! grow_slab_list(dstid))
+        return 0;
+
+    /* killing指的是要reassign那个slab, 从1开始计数 */
+    if (p->killing == 0) p->killing = 1;
+
+    /* 找到源空间的起始地址 */
+    slab = p->slab_list[p->killing - 1];
+    slab_end = (char*)slab + POWER_BLOCK;
+
+    /* 源空间里面的所有item都不要了, 清空, 关于item结构成员的细节,
+     * 参考我其他的博客.
+     */
+    for (iter = slab; iter < slab_end; (char*)iter += p->size) {
+        item *it = (item *)iter;
+        /* slabs_clsid不为0, 表示这是一个有效数据 */
+        if (it->slabs_clsid) {
+            /* refcount大于0, 表示在其他地方正在使用这个值, 目前不能删 */
+            if (it->refcount) was_busy = true;
+            /* 把item从链表里面除掉 */
+            item_unlink(it);
+        }
+    }
+
+    /* go through free list and discard items that are no longer part of this slab
+     * 下面的过程就是剔除slots里面属于src slab的内容, 这个过程还是很巧秒的.
+     */
+    {
+        int fi;
+        for (fi = p->sl_curr - 1; fi >= 0; fi--) {
+            if (p->slots[fi] >= slab && p->slots[fi] < slab_end) {
+                p->sl_curr--;
+                if (p->sl_curr > fi) p->slots[fi] = p->slots[p->sl_curr];
+            }
+        }
+    }
+
+    if (was_busy) return -1;
+
+    /* if good, now move it to the dst slab class
+     * 现在往目标slabclass迁移
+     */
+    /* 最后一个萝卜放到空出来的坑里面 */
+    p->slab_list[p->killing - 1] = p->slab_list[p->slabs - 1];
+    p->slabs--;
+    p->killing = 0;
+    /* 在目标slabclass, 把萝卜栽进去, 这里相当于新分配一个页面到目标slabclass */
+    dp->slab_list[dp->slabs++] = slab;
+    dp->end_page_ptr = slab;
+    dp->end_page_free = dp->perslab;
+    /* this isn't too critical, but other parts of the code do asserts to
+     * make sure this field is always 0. 这里再填一次0, 确保后面的东西正常
+     */
+    for (iter = slab; iter < slab_end; (char*)iter += dp->size) {
+        ((item *)iter)->slabs_clsid = 0;
+    }
+    return 1;
+}
+#endif
+
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/11/06/Memcached-event-handler.html b/2015/11/06/Memcached-event-handler.html new file mode 100644 index 0000000..77b08f2 --- /dev/null +++ b/2015/11/06/Memcached-event-handler.html @@ -0,0 +1,1200 @@ + + + +Memcached的事件处理函数 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Memcached的事件处理函数

  + +
+
+ + +

我们知道, Memcached 使用了 Libevent 库来驱动. 它用 Libevent 实现了程序自己的时钟, +以及 todelete 表(过期内容表)里面的删除. 时钟和过期删除机制在 +Memcached 初始化一节已经讨论过了. 本篇讨论更核心的, 即它的客 +户端连接请求处理.

+ + + +

在 Memcached 初始化的时候, 有这样几句代码

+ +

+
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/11/07/Memcached-item.html b/2015/11/07/Memcached-item.html new file mode 100644 index 0000000..05a76be --- /dev/null +++ b/2015/11/07/Memcached-item.html @@ -0,0 +1,1533 @@ + + + +Memcached的数据管理 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Memcached的数据管理

  + +
+
+ + +

Memcached 在收到数据时, 是以 item 的形式存放到内存里的. 这些 item 之间组成链表

+ +

item 的分配

+ +

老样子, 在源码之前, 数据结构要弄个差不多. 那么, 先来看一下 item 的结构吧.

+ + + +
typedef struct _stritem {
+    struct _stritem *next;      /* item链表里面的next指针 */
+    struct _stritem *prev;      /* item链表里面的prev指针 */
+    struct _stritem *h_next;    /* hash chain next, 哈希表里面的next指针 */
+    rel_time_t      time;       /* least recent, access 最近访问时间 */
+    rel_time_t      exptime;    /* expire time, 过期时间 */
+    int             nbytes;     /* size of data, data块的大小 */
+    unsigned short  refcount;   /* 引用计数, 大于0表示正在使用中 */
+    uint8_t         nsuffix;    /* length of flags-and-length string */
+    uint8_t         it_flags;   /* ITEM_* above */
+    uint8_t         slabs_clsid;/* which slab class we're in */
+    uint8_t         nkey;       /* key length, w/terminating null and padding */
+    void * end[0];
+    /* then null-terminated key */
+    /* then " flags length\r\n" (no terminating null) */
+    /* then data with terminating \r\n (no terminating null; it's binary!) */
+} item;
+
+ +

上面数据结构定义了 item 链表(双向链表)节点, 由于我们使用哈希表索引 key, 所以在哈 +希表里面, 还有一个链表(单链表)用于辅助存储, 查找 key 碰撞节点. 接下来是最近访问 +时间以及过期时间, 最近访问时间用于 LRU 算法, 过期时间的作用当然是指示过期数据的. +紧接着是数据块的大小, 这里的数据块是指用户给我们原始数据大小, 也就是命令行里面 +的<length>, refcount是引用计数, 可以防止误 LRU 删除我们正在使用的数据. +nsuffix这个成员比较有意思, 它记录了<flag> <length>字符串的长度, 这个 flag 是 +客户给我们的 flag, 它和it_flags不一样, 后者是 memcached 内部使用的一个标志. 用 +于标识这个 item 当前的状态, 这一点一定要搞清楚, 记明白slabs_clsid记录了自己所 +在的 slabclass, nkey 则记录了 key 的长度. 最后是一个长度为 0 的指针数组, 其实这只是 +一个标记, 这里将来会存放数据. 使用长度为 0 的数组, 好处是这个数组不占用任何内存 +空间, 但是又能帮助我们访问它后面的内存. 如果不这么用, 而把这里声明成指针, 我们 +就会多出一个指针的空间, 这种做法可以尽可能的节约内存. 这个用法有点高级, 而且标 +准 C 并不支持, 只有 GNUc 编译器才支持.

+ +

接下来是一部分宏定义, 用来操作 item 存放的数据

+ +
#define ITEM_key(item) ((char*)&((item)->end[0]))
+
+/* warning: don't use these macros with a function, as it evals its arg twice */
+#define ITEM_suffix(item) ((char*) &((item)->end[0]) + (item)->nkey + 1)
+#define ITEM_data(item) ((char*) &((item)->end[0]) + (item)->nkey + 1 + (item)->nsuffix)
+#define ITEM_ntotal(item) (sizeof(struct _stritem) + (item)->nkey + 1 + (item)->nsuffix + (item)->nbytes)
+
+ +

ITEM_key用于取得数据的 key. 由于 key 最终是以NULL结尾的, 我们只要往后找, 直到 +找到这个空字符就能找到 key 的结尾了.

+ +

ITEM_suffix用于取出<flag> <length>\r\n, 这个串的结尾并没有NULL字符, 这里 +并不会涉及到单独读取, 所以没有结束符也无所谓. 注意(item)->nkey + 1这是因为 +nkey 包含结尾的NULL字符以及补白的, 但是<flag>之前有一个空格需要我们手动略过.

+ +

ITEM_data用于读取存储的数据, 数据部分在<key>NULL <flag> <length>\r\n之后. +ITEM_ntotal用于计算这个 item 的总共大小.

+ +

OK, 接下来是 item 的使用, 刚开始, 先来点基础的东西. 下面的函数, 用于组装 suffix +以及计算 item 的大小.

+ +
/*
+ * Generates the variable-sized part of the header for an object.
+ *
+ * key     - The key
+ * nkey    - The length of the key
+ * flags   - key flags
+ * nbytes  - Number of bytes to hold value and addition CRLF terminator
+ * suffix  - Buffer for the "VALUE" line suffix (flags, size).
+ * nsuffix - The length of the suffix is stored here.
+ *
+ * Returns the total size of the header.
+ */
+static size_t item_make_header(const uint8_t nkey, const int flags, const int nbytes,
+                     char *suffix, uint8_t *nsuffix) {
+    /* suffix is defined at 40 chars elsewhere.. */
+    *nsuffix = (uint8_t) snprintf(suffix, 40, " %d %d\r\n", flags, nbytes - 2);
+    return sizeof(item) + nkey + *nsuffix + nbytes;
+}
+
+ +

接下来是 item 的内存分配, 这个函数配上slabs_alloc完成了整个存储数据需要的内存 +分配的全部过程.

+ +
/*@null@*/
+item *do_item_alloc(char *key, const size_t nkey, const int flags, const rel_time_t exptime, const int nbytes) {
+    uint8_t nsuffix;
+    item *it;
+    /* 给suffix分配空间<flag> <length>分配40个字节足够了 */
+    char suffix[40];
+    /* 拼装suffix以及计算总大小 */
+    size_t ntotal = item_make_header(nkey + 1, flags, nbytes, suffix, &nsuffix);
+
+    /* 计算ID, 我们在slab模块见过这个函数 */
+    unsigned int id = slabs_clsid(ntotal);
+    if (id == 0)
+        return 0;
+
+    /* 下面是给item分配内存, 涉及到了LRU算法, slabs_alloc在slab模块 */
+    it = slabs_alloc(ntotal);
+    /* it == 0说明分配失败, 需要考虑使用LRU算法 */
+    if (it == 0) {
+        /* LRU算法最多只尝试50次 */
+        int tries = 50;
+        item *search;
+
+        /* If requested to not push old items out of cache when memory runs out,
+         * we're out of luck at this point...
+         * 不允许使用LRU算法, 那就啥都拜拜了
+         */
+        if (settings.evict_to_free == 0) return NULL;
+
+        /*
+         * try to get one off the right LRU
+         * don't necessariuly unlink the tail because it may be locked: refcount>0
+         * search up from tail an item with refcount==0 and unlink it; give up after 50
+         * tries, 下面是LRU的核心
+         *
+         * 先检查条件, 不满足玩个毛线啊...
+         */
+        if (id > LARGEST_ID) return NULL;
+        if (tails[id] == 0) return NULL;
+
+        /* 现在从tail开始往回遍历链表, 找到那些没用的老数据, 删掉 */
+        for (search = tails[id]; tries > 0 && search != NULL; tries--, search=search->prev) {
+            if (search->refcount == 0) {
+                /* exptime > current_time意味着数据尚未过期, 这是强制剔除, 做个记录*/
+                if (search->exptime > current_time) {
+                       STATS_LOCK();
+                       stats.evictions++;
+                       STATS_UNLOCK();
+                }
+                /* 从链表里面剔除 */
+                do_item_unlink(search);
+                break;
+            }
+        }
+        /* LRU完了再次请求分配, 这次应该能拿到存储空间了(不排除其他线程抢用或
+         * 者其他特殊情况), 所以我们又检查了下分配给我们的内存
+         */
+        it = slabs_alloc(ntotal);
+        if (it == 0) return NULL;
+    }
+
+    /* 断言it是空白的 */
+    assert(it->slabs_clsid == 0);
+
+    it->slabs_clsid = id;
+
+    /* 很明显it不能是链表头, 这是绝对的 */
+    assert(it != heads[it->slabs_clsid]);
+
+    /* 现在初始化it的各项内容 */
+    it->next = it->prev = it->h_next = 0;
+    it->refcount = 1;     /* the caller will have a reference */
+    DEBUG_REFCNT(it, '*');
+    /* 此处的flag并不是客户的flag.*/
+    it->it_flags = 0;
+    /* key的大小和数据块的大小 */
+    it->nkey = nkey;
+    it->nbytes = nbytes;
+    strcpy(ITEM_key(it), key);
+    it->exptime = exptime;
+    memcpy(ITEM_suffix(it), suffix, (size_t)nsuffix);
+    it->nsuffix = nsuffix;
+    /* item已经装好了, 剩下的就是把data填到ITEM_data里面就OK了 */
+    return it;
+}
+
+ +

这里面有两个我们没有介绍过的DEBUG_REFCNT宏和do_item_unlink函数. 来看下

+ +
/* Enable this for reference-count debugging. */
+#if 0
+# define DEBUG_REFCNT(it,op) \
+                fprintf(stderr, "item %x refcnt(%c) %d %c%c%c\n", \
+                        it, op, it->refcount, \
+                        (it->it_flags & ITEM_LINKED) ? 'L' : ' ', \
+                        (it->it_flags & ITEM_SLABBED) ? 'S' : ' ', \
+                        (it->it_flags & ITEM_DELETED) ? 'D' : ' ')
+#else
+# define DEBUG_REFCNT(it,op) while(0)
+#endif
+
+ +

DEBUG_REFCNT打印出 item 的位置, 传入的控制符, refcount, flags 等信息, 容易理解. +至于DEBUG_REFCNT定义为while(0)的情况, 是一种写法, 技巧, 还有一种更好理解的 +写法是do {} while(0). 这些可自行上网搜索. 我这里也有 +一篇笔记, 内容来自TIPI项目

+ +

do_item_unlink描述如何把 item 从链表里面除去. 我们把这部分内容放到下一节介绍

+ +

对应的先介绍一下如何向链表插入数据?

+ +
int do_item_link(item *it) {
+    /* 做插入操作的item状态不能是链表中或者slab中状态 */
+    assert((it->it_flags & (ITEM_LINKED|ITEM_SLABBED)) == 0);
+    /* 数据一定不应该比1M大 */
+    assert(it->nbytes < 1048576);
+    /* 更新状态为链表中状态 */
+    it->it_flags |= ITEM_LINKED;
+    /* 最近访问时间为当前时间 */
+    it->time = current_time;
+    /* 在hash表里面插入 */
+    assoc_insert(it);
+
+    /* 统计数据更新 */
+    STATS_LOCK();
+    stats.curr_bytes += ITEM_ntotal(it);
+    stats.curr_items += 1;
+    stats.total_items += 1;
+    STATS_UNLOCK();
+
+    /* 在item双链表里面插入 */
+    item_link_q(it);
+
+    return 1;
+}
+
+static void item_link_q(item *it) { /* item is the new head */
+    /* 针对头或者尾需要特殊处理 */
+    item **head, **tail;
+    /* always true, warns: assert(it->slabs_clsid <= LARGEST_ID); */
+    /* 状态位, ITEM_SLABBED表示处于slab管理状态, 也即空闲待使用状态 */
+    assert((it->it_flags & ITEM_SLABBED) == 0);
+
+    head = &heads[it->slabs_clsid];
+    tail = &tails[it->slabs_clsid];
+
+    /* 插入的肯定不是头元素? */
+    assert(it != *head);
+
+    /* head以及tail要么同时有(正常)或者都没有(未初始化) */
+    assert((*head && *tail) || (*head == 0 && *tail == 0));
+
+    /* 新插入的就是头部元素, 当前头部成了老二 */
+    it->prev = 0;
+    it->next = *head;
+
+    /* 当原来的head不为空, 那么它的前节点就是it */
+    if (it->next) it->next->prev = it;
+    /* 现在head就是it啦 */
+    *head = it;
+
+    /* 如果tail不存在, 那么tail就是it */
+    if (*tail == 0) *tail = it;
+    /* 统计数组+1 */
+    sizes[it->slabs_clsid]++;
+    return;
+}
+
+ +

item 的回收

+ +

现在来看, 如何从链表剔除数据

+ +
void do_item_unlink(item *it) {
+    /* ITEM_LINKED这个状态位标志着item在当前linklist里 */
+    if ((it->it_flags & ITEM_LINKED) != 0) {
+        /* 除去在linklist的标志 */
+        it->it_flags &= ~ITEM_LINKED;
+        STATS_LOCK();
+        /* 统计数据作相应的改动 */
+        stats.curr_bytes -= ITEM_ntotal(it);
+        stats.curr_items -= 1;
+        STATS_UNLOCK();
+        /* 到哈希表里面删除item */
+        assoc_delete(ITEM_key(it), it->nkey);
+        /* 在链表里面删除item */
+        item_unlink_q(it);
+        /* 如果其他地方没有引用, 删掉它 */
+        if (it->refcount == 0) item_free(it);
+    }
+}
+
+ +

这里涉及到了三个调用

+ +
    +
  1. assoc_delete用于从哈希表里面剔除数据, 我们在哈希表讲解
  2. +
  3. item_unlink_q用于从 item 链表删除节点, 我们马上就会谈到这个函数
  4. +
  5. item_free用于释放 chunk 到 slots, 马上介绍
  6. +
+ +

上面的函数其实不能算完成了把数据从链表剔除, 它只是把工作交给了item_unlink_q +函数去完成, item_unlink_q的代码如下, 这点代码就是个双向链表删除节点的过程, +我们都写烂了这段代码, 所以没加注释.

+ +
static void item_unlink_q(item *it) {
+    item **head, **tail;
+    /* always true, warns: assert(it->slabs_clsid <= LARGEST_ID); */
+    head = &heads[it->slabs_clsid];
+    tail = &tails[it->slabs_clsid];
+
+    if (*head == it) {
+        assert(it->prev == 0);
+        *head = it->next;
+    }
+
+    if (*tail == it) {
+        assert(it->next == 0);
+        *tail = it->prev;
+    }
+    assert(it->next != it);
+    assert(it->prev != it);
+
+    if (it->next) it->next->prev = it->prev;
+    if (it->prev) it->prev->next = it->next;
+    sizes[it->slabs_clsid]--;
+    return;
+}
+
+ +

接下来释放 item 空间, 交给 slab 管理

+ +
void item_free(item *it) {
+    size_t ntotal = ITEM_ntotal(it);
+    /* 释放的时候, item肯定不再链表中了 */
+    assert((it->it_flags & ITEM_LINKED) == 0);
+    /* 更不可能是链表的头尾元素 */
+    assert(it != heads[it->slabs_clsid]);
+    assert(it != tails[it->slabs_clsid]);
+    /* 应该也没有人使用这块数据 */
+    assert(it->refcount == 0);
+
+    /* so slab size changer can tell later if item is already free or not
+     * 这里就是传说中的专门设置slabs_clsid = 0的地方
+     */
+    it->slabs_clsid = 0;
+    /* 加上slab标签, 表示当前chunk由slab管理, 出于空闲状态
+     * TODO: 系统的总结一下这几个状态位
+     */
+    it->it_flags |= ITEM_SLABBED;
+    DEBUG_REFCNT(it, 'F');
+    slabs_free(it, ntotal);
+}
+
+ +

到这里, item 相关的东西就说了个差不多了, 还有一部分是统计相关的代码, 拿到统计 +部分去说, 后面的代码比较晦涩, 等到需要的时候再说.

+ +
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/11/08/Memcached-assoc-xxx.html b/2015/11/08/Memcached-assoc-xxx.html new file mode 100644 index 0000000..eedad4e --- /dev/null +++ b/2015/11/08/Memcached-assoc-xxx.html @@ -0,0 +1,1437 @@ + + + +Memcached的哈希表 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Memcached的哈希表

  + +
+
+ + +

Memcached 在收到 key 时, 会把它哈希到自己的哈希表里面去. 这篇文章分析哈希表.

+ +

首先说一下 memcached 用到的哈希算法, 是一个叫做 Bob Jenkins 的牛人提出的, 代码也是 +这位高人写得, 这里提供他的网页链接

+ +

剔除掉这位高手写得哈希算法, memcached 实际上在这里没做多少事, 主要就是实现了哈 +希表的管理

+ + + +

哈希表初始化

+ +
typedef  unsigned long  int  ub4;   /* unsigned 4-byte quantities */
+typedef  unsigned       char ub1;   /* unsigned 1-byte quantities */
+
+/* how many powers of 2's worth of buckets we use */
+static int hashpower = 16;
+
+#define hashsize(n) ((ub4)1<<(n))
+#define hashmask(n) (hashsize(n)-1)
+
+/* Main hash table. This is where we look except during expansion. */
+static item** primary_hashtable = 0;
+
+/*
+ * Previous hash table. During expansion, we look here for keys that haven't
+ * been moved over to the primary yet.
+ */
+static item** old_hashtable = 0;
+
+/* Number of items in the hash table. */
+static int hash_items = 0;
+
+/* Flag: Are we in the middle of expanding now? */
+static int expanding = 0;
+
+/*
+ * During expansion we migrate values with bucket granularity; this is how
+ * far we've gotten so far. Ranges from 0 .. hashsize(hashpower - 1) - 1.
+ */
+static int expand_bucket = 0;
+
+/* 填充哈希桶 */
+void assoc_init(void) {
+    unsigned int hash_size = hashsize(hashpower) * sizeof(void*);
+    primary_hashtable = malloc(hash_size);
+    if (! primary_hashtable) {
+        fprintf(stderr, "Failed to init hashtable.\n");
+        exit(EXIT_FAILURE);
+    }
+    memset(primary_hashtable, 0, hash_size);
+}
+
+ +

最后集中解释一下下面一直出现的一句代码

+ +
(oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
+
+ +

这表示, 在目前的迁移工作中, 已经迁移到了expand_bucket, 但是我们新插入的数据, +还可以插入到老的哈希表里面, 等待稍后一起随老数据迁移. 不满足此条件的新数据, 都 +会被插入到primary_hashtable里面去

+ +

寻找

+ +

接下来这个函数简直就是_hashitem_before的翻版, 不过他返回一个指向 item 的指针.

+ +
item *assoc_find(const char *key, const size_t nkey) {
+    uint32_t hv = hash(key, nkey, 0);
+    item *it;
+    int oldbucket;
+
+    if (expanding &&
+        (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
+    {
+        it = old_hashtable[oldbucket];
+    } else {
+        it = primary_hashtable[hv & hashmask(hashpower)];
+    }
+
+    while (it) {
+        if ((nkey == it->nkey) &&
+            (memcmp(key, ITEM_key(it), nkey) == 0)) {
+            return it;
+        }
+        it = it->h_next;
+    }
+    return 0;
+}
+
+ +

删除

+ +

下面是根据 key 和 nkey 在 hash 表里面搜索的过程, 一系列assoc_XXX函数基本上都用到 +这个东西了

+ +

注意这里返回的是一个二阶指针, 当我们第一次对这个二阶指针解引用, 得到的是 item +之前那个 item 的h_next成员, 再次解引用才是我们要操作的 item.

+ +
/* returns the address of the item pointer before the key.  if *item == 0,
+ * the item wasn't found
+ */
+
+static item** _hashitem_before (const char *key, const size_t nkey) {
+    /* 得到hash值, 在hash表里面的存储地址是hv & hasmmask(hashpower) */
+    uint32_t hv = hash(key, nkey, 0);
+
+    item **pos;
+    int oldbucket;
+
+    /* 处于扩容模式, 我们要从old_hashtable里面找数据 */
+    if (expanding &&
+        (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
+    {
+        pos = &old_hashtable[oldbucket];
+    } else {
+        /* 正常情况是在这里的, 找到对应的哈希链表 */
+        pos = &primary_hashtable[hv & hashmask(hashpower)];
+    }
+
+    /* 这一步是在hash链表里面找key, 并返回它的地址 */
+    while (*pos && ((nkey != (*pos)->nkey) || memcmp(key, ITEM_key(*pos), nkey))) {
+        pos = &(*pos)->h_next;
+    }
+    return pos;
+}
+
+ +

删除配合_hashitem_before使用. 达到了从链表里面删除 item 的目地.

+ +
void assoc_delete(const char *key, const size_t nkey) {
+    item **before = _hashitem_before(key, nkey);
+
+    if (*before) {
+        item *nxt = (*before)->h_next;
+        (*before)->h_next = 0;   /* probably pointless, but whatever. */
+        *before = nxt;
+        hash_items--;
+        return;
+    }
+    /* Note:  we never actually get here.  the callers don't delete things
+       they can't find. */
+    assert(*before != 0);
+}
+
+ +

插入

+ +

相比之前的版本, 哈希表在插入时多了扩容的选择, 挺好.

+ +
/* Note: this isn't an assoc_update.  The key must not already exist to call this */
+int assoc_insert(item *it) {
+    uint32_t hv;
+    int oldbucket;
+
+    /* shouldn't have duplicately named things defined */
+    assert(assoc_find(ITEM_key(it), it->nkey) == 0);
+
+    hv = hash(ITEM_key(it), it->nkey, 0);
+    /* 处在扩容模式, 插入到old_hashtable, 否则插入到primary_hashtable
+     * 数据插入都是在头部插入
+     */
+    if (expanding &&
+        (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
+    {
+        it->h_next = old_hashtable[oldbucket];
+        old_hashtable[oldbucket] = it;
+    } else {
+        it->h_next = primary_hashtable[hv & hashmask(hashpower)];
+        primary_hashtable[hv & hashmask(hashpower)] = it;
+    }
+
+    hash_items++;
+    /* 决定是不是需要给哈希表扩容, 哈希表里面的数据超过hash表的1.5倍就要扩容
+     * 这个概念就大概相当于每查找一个item, 大约要比较1.5个item才能找到
+     */
+    if (! expanding && hash_items > (hashsize(hashpower) * 3) / 2) {
+        assoc_expand();
+    }
+
+    return 1;
+}
+
+ +

扩容/迁移数据

+ +

扩容的目的是减少哈希碰撞, 提高检索效率.

+ +

警告: 出于多线程效率考虑, 这里的迁移一次调用只迁移了一个桶. 原因是锁的粒 +度过大, 迁移的时候实际上整张哈希表都加了锁, 不宜太长时间操作.

+ +
/* grows the hashtable to the next power of 2. */
+static void assoc_expand(void) {
+    old_hashtable = primary_hashtable;
+
+    /* 新hash表的大小是老的的两倍 */
+    primary_hashtable = calloc(hashsize(hashpower + 1), sizeof(void *));
+    if (primary_hashtable) {
+        if (settings.verbose > 1)
+            fprintf(stderr, "Hash table expansion starting\n");
+        /* 这个+1操作也就到这里才敢玩, if块之外玩, 容易出事 */
+        hashpower++;
+        /* 扩容中标识 */
+        expanding = 1;
+        /* 这个变量指示那些哈希桶已经迁移走了, 帮助我们在迁移阶段正确的向
+         * old_hashtable或者primary_hashtable插入数据.
+         */
+        expand_bucket = 0;
+        do_assoc_move_next_bucket();
+    } else {
+        /* 这里就是说不能扩容啦 */
+        primary_hashtable = old_hashtable;
+        /* Bad news, but we can keep running. */
+    }
+}
+
+/* migrates the next bucket to the primary hashtable if we're expanding.
+ *
+ * 但是这桶迁移只迁移了一只桶
+ */
+void do_assoc_move_next_bucket(void) {
+    item *it, *next;
+    int bucket;
+
+    if (expanding) {
+        /* 老表里面的数据, 迁移到新表里面去 */
+        for (it = old_hashtable[expand_bucket]; NULL != it; it = next) {
+            next = it->h_next;
+
+            bucket = hash(ITEM_key(it), it->nkey, 0) & hashmask(hashpower);
+            it->h_next = primary_hashtable[bucket];
+            primary_hashtable[bucket] = it;
+        }
+
+        /* 老表的这个桶清空 */
+        old_hashtable[expand_bucket] = NULL;
+
+        expand_bucket++;
+        /* 已经迁移完了, 释放老表空间 */
+        if (expand_bucket == hashsize(hashpower - 1)) {
+            expanding = 0;
+            free(old_hashtable);
+            if (settings.verbose > 1)
+                fprintf(stderr, "Hash table expansion done\n");
+        }
+    }
+}
+
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/11/13/Memcached-threads.html b/2015/11/13/Memcached-threads.html new file mode 100644 index 0000000..eb28e31 --- /dev/null +++ b/2015/11/13/Memcached-threads.html @@ -0,0 +1,1693 @@ + + + +Memcached多线程分析 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Memcached多线程分析

  + +
+
+ + +

从 1.2.2 版本开始, memcached 引入了多线程. 这里我们讨论的就是第一个多线程版本. +关于这个版本的多线程, 实现的其实很粗糙, 锁的粒度都很大.

+ +

说一下这里的多线程相关函数, 我觉得我自己就够水笔了, 如果你看不懂这些调用, 就去看书吧…

+ +

相关的数据结构

+ +

多线程用的的一些数据结构, 这里的 CQ 即是Connect Queue的缩写.

+ +

下面这个结构体, 定义了 connection 队列里面的一些列套接字和与之相关的信息.

+ + + +
/* An item in the connection queue. */
+typedef struct conn_queue_item CQ_ITEM;
+struct conn_queue_item {
+    int     sfd;
+    int     init_state;
+    int     event_flags;
+    int     read_buffer_size;
+    int     is_udp;
+    CQ_ITEM *next;
+};
+
+ +

接下来这个是链接请求的队列. 队列有一个条件锁一个互斥锁, 作用在以后再谈.

+ +
/* A connection queue. */
+typedef struct conn_queue CQ;
+struct conn_queue {
+    CQ_ITEM *head;
+    CQ_ITEM *tail;
+    pthread_mutex_t lock;
+    pthread_cond_t  cond;
+};
+
+ +

然后, 我们声明了一些全局使用的锁. 感觉到处都是锁啊, 写多线程果然不易 :(.

+ +
/* Lock for connection freelist
+* 连接队列锁
+*/
+static pthread_mutex_t conn_lock;
+
+/* Lock for cache operations (item_*, assoc_*)
+* 处理数据时候上的操作锁
+*/
+static pthread_mutex_t cache_lock;
+
+/* Lock for slab allocator operations
+* slab分配空间时的锁
+*/
+static pthread_mutex_t slabs_lock;
+
+/* Lock for global stats
+* 显示统计状态的全局锁
+*/
+static pthread_mutex_t stats_lock;
+
+/* Free list of CQ_ITEM structs
+ * 空闲连接回收列表以及它的锁
+ */
+static CQ_ITEM *cqi_freelist;
+static pthread_mutex_t cqi_freelist_lock;
+
+ +

这个结构体可以算是线程池了吧, 不知道这么说准不准确. +libevent 自身并不提供线程间通讯, 所以, 我们需要自己来处理. 这里的处理方法是利用管道通讯, 我找了一篇 +不错的文章

+ +
/*
+ * Each libevent instance has a wakeup pipe, which other threads
+ * can use to signal that they've put a new connection on its queue.
+ */
+typedef struct {
+    pthread_t thread_id;        /* unique ID of this thread */
+    struct event_base *base;    /* libevent handle this thread uses */
+    struct event notify_event;  /* listen event for notify pipe */
+    int notify_receive_fd;      /* receiving end of notify pipe */
+    int notify_send_fd;         /* sending end of notify pipe */
+    CQ  new_conn_queue;         /* queue of new connections to handle */
+} LIBEVENT_THREAD;
+
+/* 线程池 */
+static LIBEVENT_THREAD *threads;
+
+/*
+ * Number of threads that have finished setting themselves up.
+ * 当前已经初始化完成的线程个数, 也就是已经在服务状态的线程个数
+ */
+static int init_count = 0;
+/* 下面两个锁是来锁上面一个变量的 */
+static pthread_mutex_t init_lock;
+static pthread_cond_t init_cond;
+
+ +

初始化

+ +

连接队列的初始化

+ +

初始化连接队列, 以及它里面的锁

+ +
/*
+ * Initializes a connection queue.
+ */
+static void cq_init(CQ *cq) {
+    pthread_mutex_init(&cq->lock, NULL);
+    pthread_cond_init(&cq->cond, NULL);
+    cq->head = NULL;
+    cq->tail = NULL;
+}
+
+ +

初始化多线程

+ +

这里我们会遇到麻烦, libevent 不能支持线程间通讯, 所以需要自己实现. 每一个线程都有自己的 base event 用于处理事件相关的逻辑.

+ +

我们先来看, 在线程初始化的thread_init会被调用的几个基础函数, 理解这些函数, 有助于理解 memcached 的多线程实现.

+ +

下面这段代码, 实现了各个线程自己的 base event.

+ +
/*
+ * Set up a thread's information.
+ * 每一个线程都有自己的base event. 相互之间用管道来通讯
+ */
+static void setup_thread(LIBEVENT_THREAD *me) {
+    if (! me->base) {
+        me->base = event_init();
+        if (! me->base) {
+            fprintf(stderr, "Can't allocate event base\n");
+            exit(1);
+        }
+    }
+
+    /* Listen for notifications from other threads */
+    event_set(&me->notify_event, me->notify_receive_fd,
+              EV_READ | EV_PERSIST, thread_libevent_process, me);
+    event_base_set(me->base, &me->notify_event);
+
+    if (event_add(&me->notify_event, 0) == -1) {
+        fprintf(stderr, "Can't monitor libevent notify pipe\n");
+        exit(1);
+    }
+
+    /* 初始化自己的连接队列 */
+    cq_init(&me->new_conn_queue);
+}
+
+ +

这段代码创建所谓的worker线程, 说白了, 这些 worker 就是干活的, 主线程才是大老板, 是分派任务的.

+ +
/*
+ * Creates a worker thread.
+ */
+static void create_worker(void *(*func)(void *), void *arg) {
+    pthread_t       thread;
+    pthread_attr_t  attr;
+    int             ret;
+
+    pthread_attr_init(&attr);
+
+    if (ret = pthread_create(&thread, &attr, func, arg)) {
+        fprintf(stderr, "Can't create thread: %s\n",
+                strerror(ret));
+        exit(1);
+    }
+}
+
+/*
+ * Worker thread: main event loop
+ * 此函数是每个线程的入口函数, 在这里和thread_init函数配合完成线程的初始化以及进入事件循环
+ */
+static void *worker_libevent(void *arg) {
+    LIBEVENT_THREAD *me = arg;
+
+    /* Any per-thread setup can happen here; thread_init() will block until
+     * all threads have finished initializing.
+     */
+
+    pthread_mutex_lock(&init_lock);
+    init_count++;
+    pthread_cond_signal(&init_cond);
+    pthread_mutex_unlock(&init_lock);
+
+    event_base_loop(me->base, 0);
+}
+
+ +

下面来看是如何初始化多线程的.

+ +
/*
+ * Initializes the thread subsystem, creating various worker threads.
+ *
+ * nthreads  Number of event handler threads to spawn
+ * main_base Event base for main thread
+ */
+void thread_init(int nthreads, struct event_base *main_base) {
+    int         i;
+    pthread_t   *thread;
+
+    /* 初始化锁, 没啥看头 */
+    pthread_mutex_init(&cache_lock, NULL);
+    pthread_mutex_init(&conn_lock, NULL);
+    pthread_mutex_init(&slabs_lock, NULL);
+    pthread_mutex_init(&stats_lock, NULL);
+
+    pthread_mutex_init(&init_lock, NULL);
+    pthread_cond_init(&init_cond, NULL);
+
+    pthread_mutex_init(&cqi_freelist_lock, NULL);
+    /* 注意这里, 我们把空闲连接回收资源初始化为空 */
+    cqi_freelist = NULL;
+
+    /* 开始初始化线程 */
+    threads = malloc(sizeof(LIBEVENT_THREAD) * nthreads);
+    if (! threads) {
+        perror("Can't allocate thread descriptors");
+        exit(1);
+    }
+
+    /* 规定第一个线程是主线程, 方便我们以后找到它 */
+    threads[0].base = main_base;
+    threads[0].thread_id = pthread_self(); /* 获得自身的线程ID */
+
+    for (i = 0; i < nthreads; i++) {
+        int fds[2];
+        /* 创建了线程间用于通讯的管道 */
+        if (pipe(fds)) {
+            perror("Can't create notify pipe");
+            exit(1);
+        }
+
+        threads[i].notify_receive_fd = fds[0];
+        threads[i].notify_send_fd = fds[1];
+
+        /* 执行剩余的设定工作
+         * 主要是libevent相关的, 不支持线程间通讯还真是麻烦啊
+         */
+        setup_thread(&threads[i]);
+    }
+
+    /* Create threads after we've done all the libevent setup.
+     * 下面才是真正的初始化线程池工作
+     */
+    for (i = 1; i < nthreads; i++) {
+        create_worker(worker_libevent, &threads[i]);
+    }
+
+    /* Wait for all the threads to set themselves up before returning. */
+    pthread_mutex_lock(&init_lock);
+    /* main thread, 这次自加代表主线程,
+     * 以后init_count在worker_libevent函数中执行
+     */
+    init_count++;
+    while (init_count < nthreads) {
+        pthread_cond_wait(&init_cond, &init_lock);
+    }
+    pthread_mutex_unlock(&init_lock);
+}
+
+/* start up worker threads if MT mode */
+thread_init(settings.num_threads, main_base);
+
+ +

至此, memcached 的多线程初始化完毕, 各个线程在自己的事件循环中等待来自主 +线程的任务分配(通过 pipe), 当收到任务时, 执行thread_libevent_process函数 +处理之.

+ +

多线程的任务分配

+ +

子线程自己是不能主动响应客户端请求的, 主线程通知了, 它才会去处理. 它通过监听 +自己的notify_send_fd描述符来获得通知.

+ +

分配任务时, 会调用cqi_new来创建一个新的CQ_ITEM(队列节点), 下面是这个函数 +的源代码

+ +
#define ITEMS_PER_ALLOC 64
+
+/*
+ * Returns a fresh connection queue item.
+ */
+static CQ_ITEM *cqi_new() {
+    CQ_ITEM *item = NULL;
+    /* 闲置资源里面有, 那么优先使用闲置资源 */
+    pthread_mutex_lock(&cqi_freelist_lock);
+    if (cqi_freelist) {
+        item = cqi_freelist;
+        cqi_freelist = item->next;
+    }
+    pthread_mutex_unlock(&cqi_freelist_lock);
+
+    /* 没有闲置资源的情况下, 就需要向系统请求分配, 这里分配也不是
+     * 一个一个分配, 那样太没效率, 我们一次分配ITEMS_PER_ALLOC. 然后把用不完的
+     * 放到闲置资源列表, 以后当闲置资源使用
+     */
+    if (NULL == item) {
+        int i;
+
+        /* Allocate a bunch of items at once to reduce fragmentation */
+        item = malloc(sizeof(CQ_ITEM) * ITEMS_PER_ALLOC);
+        if (NULL == item)
+            return NULL;
+
+        /*
+         * Link together all the new items except the first one
+         * (which we'll return to the caller) for placement on
+         * the freelist.
+         */
+        for (i = 2; i < ITEMS_PER_ALLOC; i++)
+            item[i - 1].next = &item[i];
+
+        /* 然后, 把原来的空闲资源列表放到新的资源列表之后, 作为新的
+         * 闲置资源列表
+         */
+        pthread_mutex_lock(&cqi_freelist_lock);
+        item[ITEMS_PER_ALLOC - 1].next = cqi_freelist;
+        cqi_freelist = &item[1];
+        pthread_mutex_unlock(&cqi_freelist_lock);
+    }
+
+    return item;
+}
+
+ +

当调度函数准备好任务之后, 就将CQ_ITEM放到CQ里面, 这个功能是通过 +cq_push函数来完成的.

+ +
/*
+ * Adds an item to a connection queue.
+ * 把一个新的请求信息压倒队列里
+ */
+static void cq_push(CQ *cq, CQ_ITEM *item) {
+    item->next = NULL;
+
+    pthread_mutex_lock(&cq->lock);
+    if (NULL == cq->tail)
+        cq->head = item;
+    else
+        cq->tail->next = item;
+    cq->tail = item;
+    pthread_cond_signal(&cq->cond);
+    pthread_mutex_unlock(&cq->lock);
+}
+
+ +

对应的, 我们贴上弹出 item 的代码, 在这个版本的 memcached 里面, 这个函数实际上 +没有被调用过. 弹出操作是通过cq_peek来完成的, 两者的区别在于, cq_pop在 +取不到数据的情况下, 会等待条件锁(阻塞到这里了), 而cq_peek则会直接跳过

+ +
/*
+ * Waits for work on a connection queue.
+ */
+static CQ_ITEM *cq_pop(CQ *cq) {
+    CQ_ITEM *item;
+
+    pthread_mutex_lock(&cq->lock);
+    while (NULL == cq->head)
+        pthread_cond_wait(&cq->cond, &cq->lock);
+    item = cq->head;
+    cq->head = item->next;
+    if (NULL == cq->head)
+        cq->tail = NULL;
+    pthread_mutex_unlock(&cq->lock);
+
+    return item;
+}
+
+ +

之后通过向 pipe 写入一字节无意义信息, 触发子线程的监听事件, 这样子线程就知道, 哦, 该去搬砖了.

+ +
/* Which thread we assigned a connection to most recently.
+ * 调度游标, 能保证几个子线程都分到差不多数量的任务
+ */
+static int last_thread = -1;
+
+/*
+ * Dispatches a new connection to another thread. This is only ever called
+ * from the main thread, either during initialization (for UDP) or because
+ * of an incoming connection.
+ */
+void dispatch_conn_new(int sfd, int init_state, int event_flags,
+                       int read_buffer_size, int is_udp) {
+    /* 申请一个新的连接队列节点 */
+    CQ_ITEM *item = cqi_new();
+    /* 这里是调度算法, 很简单, 取余 */
+    int thread = (last_thread + 1) % settings.num_threads;
+
+    last_thread = thread;
+
+    /* 向item写入相应的初始变量 */
+    item->sfd = sfd;
+    item->init_state = init_state;
+    item->event_flags = event_flags;
+    item->read_buffer_size = read_buffer_size;
+    item->is_udp = is_udp;
+
+    /* 把这个连接请求放到队列末尾, 排队去 */
+    cq_push(&threads[thread].new_conn_queue, item);
+    /* 通知线程, 你有活干了, 来搬砖 */
+    if (write(threads[thread].notify_send_fd, "", 1) != 1) {
+        perror("Writing to thread notify pipe");
+    }
+}
+
+ +

多线程工作过程

+ +

子线程响应 pipe 可读事件(即上文的搬砖通知), libevent 库会调用在 +setup_thread(由thread_init调用)注册给它的thread_libevent_process +函数作为入口开始处理请求. 在这个过程中, 它会到请求连接队列, 获取一个客户连接, +然后响应这个客户的请求.

+ +

cq_peek用于从连接队列获取一个客户端请求套接字.

+ +
/*
+ * Looks for an item on a connection queue, but doesn't block if there isn't
+ * one. 此函数用于在连接队列里面取出一个连接请求.
+ */
+static CQ_ITEM *cq_peek(CQ *cq) {
+    CQ_ITEM *item;
+
+    pthread_mutex_lock(&cq->lock);
+    item = cq->head;
+    if (NULL != item) {
+        cq->head = item->next;
+        if (NULL == cq->head)
+            cq->tail = NULL;
+    }
+    pthread_mutex_unlock(&cq->lock);
+
+    return item;
+}
+
+ +

拿到了这个套接字, 就会调用公用的conn_new初始化这个套接字, 这个过程包括 +绑定客户端响应请求(这里会触发event_handler函数来处理此套接字后续请求), +以及初始化缓冲区等一系列初始化, 我们在Memcached 响应请求详细讨论.

+ +

最后, 当任务完成, 调用cqi_free把这个连接请求资源放到可回收列表, 用于再利用. +我们在conn_new里面已经把item各项有用的数据复制了一份到返回值, 所以这里删除item是安全的, 不会出问题.

+ +
/*
+ * Frees a connection queue item (adds it to the freelist.)
+ */
+static void cqi_free(CQ_ITEM *item) {
+    pthread_mutex_lock(&cqi_freelist_lock);
+    item->next = cqi_freelist;
+    cqi_freelist = item;
+    pthread_mutex_unlock(&cqi_freelist_lock);
+}
+
+ +

下面是thread_libevent_process函数的源码

+ +
/*
+ * Processes an incoming "handle a new connection" item. This is called when
+ * input arrives on the libevent wakeup pipe.
+ * 线程的pipe文件描述符收到EV_READ事件通知, 就会调用这个函数, 作为入口, 开始处理一个请求
+ */
+static void thread_libevent_process(int fd, short which, void *arg) {
+    LIBEVENT_THREAD *me = arg;
+    CQ_ITEM *item;
+    char buf[1];
+
+    /* 为了通知到本线程有新任务, dispatch_conn_new调度函数向管道写入了一个空字节 */
+    if (read(fd, buf, 1) != 1)
+        if (settings.verbose > 0)
+            fprintf(stderr, "Can't read from libevent pipe\n");
+
+    if (item = cq_peek(&me->new_conn_queue)) {
+        conn *c = conn_new(item->sfd, item->init_state, item->event_flags,
+                           item->read_buffer_size, item->is_udp, me->base);
+        if (!c) {
+            if (item->is_udp) {
+                fprintf(stderr, "Can't listen for events on UDP socket\n");
+                exit(1);
+            } else {
+                if (settings.verbose > 0) {
+                    fprintf(stderr, "Can't listen for events on fd %d\n",
+                            item->sfd);
+                }
+                close(item->sfd);
+            }
+        }
+        cqi_free(item);
+    }
+}
+
+ +

注意:

+ +
    +
  • CQ是每个线程独有的, 调度函数把任务写进去, 线程收到通知, 就会去队列 +里面取出任务来处理.
  • +
  • cqi_freelist是大伙公用的
  • +
+ +

结束

+ +

至此, 多线程的内容基本上已经搞定了, memcached 的源码里面还有一部分工作, 是设 +置了一些列的线程安全函数, 做法是封装相应的功能, 在开始操作之前加锁, 功能函数 +完成退出解锁.

+ +

关于这部分函数, 这里有一个对照, 帮助理解资源的锁定和锁的粒度.

+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/11/14/Memcached-locks.html b/2015/11/14/Memcached-locks.html new file mode 100644 index 0000000..f868bbd --- /dev/null +++ b/2015/11/14/Memcached-locks.html @@ -0,0 +1,1419 @@ + + + +Memcached线程安全函数列表 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Memcached线程安全函数列表

  + +
+
+ + +
/*
+ * Pulls a conn structure from the freelist, if one is available.
+ */
+conn *mt_conn_from_freelist() {
+    conn *c;
+
+    pthread_mutex_lock(&conn_lock);
+    c = do_conn_from_freelist();
+    pthread_mutex_unlock(&conn_lock);
+
+    return c;
+}
+
+ + + +
/*
+ * Adds a conn structure to the freelist.
+ *
+ * Returns 0 on success, 1 if the structure couldn't be added.
+ */
+int mt_conn_add_to_freelist(conn *c) {
+    int result;
+
+    pthread_mutex_lock(&conn_lock);
+    result = do_conn_add_to_freelist(c);
+    pthread_mutex_unlock(&conn_lock);
+
+    return result;
+}
+
+/*
+ * Returns true if this is the thread that listens for new TCP connections.
+ */
+int mt_is_listen_thread() {
+    return pthread_self() == threads[0].thread_id;
+}
+
+/*
+ * Walks through the list of deletes that have been deferred because the items
+ * were locked down at the tmie.
+ */
+void mt_run_deferred_deletes() {
+    pthread_mutex_lock(&cache_lock);
+    do_run_deferred_deletes();
+    pthread_mutex_unlock(&cache_lock);
+}
+
+/*
+ * Allocates a new item.
+ */
+item *mt_item_alloc(char *key, size_t nkey, int flags, rel_time_t exptime, int nbytes) {
+    item *it;
+    pthread_mutex_lock(&cache_lock);
+    it = do_item_alloc(key, nkey, flags, exptime, nbytes);
+    pthread_mutex_unlock(&cache_lock);
+    return it;
+}
+
+/*
+ * Returns an item if it hasn't been marked as expired or deleted,
+ * lazy-expiring as needed.
+ */
+item *mt_item_get_notedeleted(char *key, size_t nkey, bool *delete_locked) {
+    item *it;
+    pthread_mutex_lock(&cache_lock);
+    it = do_item_get_notedeleted(key, nkey, delete_locked);
+    pthread_mutex_unlock(&cache_lock);
+    return it;
+}
+
+/*
+ * Returns an item whether or not it's been marked as expired or deleted.
+ */
+item *mt_item_get_nocheck(char *key, size_t nkey) {
+    item *it;
+
+    pthread_mutex_lock(&cache_lock);
+    it = assoc_find(key, nkey);
+    it->refcount++;
+    pthread_mutex_unlock(&cache_lock);
+    return it;
+}
+
+/*
+ * Links an item into the LRU and hashtable.
+ */
+int mt_item_link(item *item) {
+    int ret;
+
+    pthread_mutex_lock(&cache_lock);
+    ret = do_item_link(item);
+    pthread_mutex_unlock(&cache_lock);
+    return ret;
+}
+
+/*
+ * Decrements the reference count on an item and adds it to the freelist if
+ * needed.
+ */
+void mt_item_remove(item *item) {
+    pthread_mutex_lock(&cache_lock);
+    do_item_remove(item);
+    pthread_mutex_unlock(&cache_lock);
+}
+
+/*
+ * Replaces one item with another in the hashtable.
+ */
+int mt_item_replace(item *old, item *new) {
+    int ret;
+
+    pthread_mutex_lock(&cache_lock);
+    ret = do_item_replace(old, new);
+    pthread_mutex_unlock(&cache_lock);
+    return ret;
+}
+
+/*
+ * Unlinks an item from the LRU and hashtable.
+ */
+void mt_item_unlink(item *item) {
+    pthread_mutex_lock(&cache_lock);
+    do_item_unlink(item);
+    pthread_mutex_unlock(&cache_lock);
+}
+
+/*
+ * Moves an item to the back of the LRU queue.
+ */
+void mt_item_update(item *item) {
+    pthread_mutex_lock(&cache_lock);
+    do_item_update(item);
+    pthread_mutex_unlock(&cache_lock);
+}
+
+/*
+ * Adds an item to the deferred-delete list so it can be reaped later.
+ */
+char *mt_defer_delete(item *item, time_t exptime) {
+    char *ret;
+
+    pthread_mutex_lock(&cache_lock);
+    ret = do_defer_delete(item, exptime);
+    pthread_mutex_unlock(&cache_lock);
+    return ret;
+}
+
+/*
+ * Does arithmetic on a numeric item value.
+ */
+char *mt_add_delta(item *item, int incr, unsigned int delta, char *buf) {
+    char *ret;
+
+    pthread_mutex_lock(&cache_lock);
+    ret = do_add_delta(item, incr, delta, buf);
+    pthread_mutex_unlock(&cache_lock);
+    return ret;
+}
+
+/*
+ * Stores an item in the cache (high level, obeys set/add/replace semantics)
+ */
+int mt_store_item(item *item, int comm) {
+    int ret;
+
+    pthread_mutex_lock(&cache_lock);
+    ret = do_store_item(item, comm);
+    pthread_mutex_unlock(&cache_lock);
+    return ret;
+}
+
+/*
+ * Flushes expired items after a flush_all call
+ */
+void mt_item_flush_expired() {
+    pthread_mutex_lock(&cache_lock);
+    do_item_flush_expired();
+    pthread_mutex_unlock(&cache_lock);
+}
+
+void mt_assoc_move_next_bucket() {
+    pthread_mutex_lock(&cache_lock);
+    do_assoc_move_next_bucket();
+    pthread_mutex_unlock(&cache_lock);
+}
+
+void *mt_slabs_alloc(size_t size) {
+    void *ret;
+
+    pthread_mutex_lock(&slabs_lock);
+    ret = do_slabs_alloc(size);
+    pthread_mutex_unlock(&slabs_lock);
+    return ret;
+}
+
+void mt_slabs_free(void *ptr, size_t size) {
+    pthread_mutex_lock(&slabs_lock);
+    do_slabs_free(ptr, size);
+    pthread_mutex_unlock(&slabs_lock);
+}
+
+char *mt_slabs_stats(int *buflen) {
+    char *ret;
+
+    pthread_mutex_lock(&slabs_lock);
+    ret = do_slabs_stats(buflen);
+    pthread_mutex_unlock(&slabs_lock);
+    return ret;
+}
+
+#ifdef ALLOW_SLABS_REASSIGN
+int mt_slabs_reassign(unsigned char srcid, unsigned char dstid) {
+    int ret;
+
+    pthread_mutex_lock(&slabs_lock);
+    ret = do_slabs_reassign(srcid, dstid);
+    pthread_mutex_unlock(&slabs_lock);
+    return ret;
+}
+#endif
+
+void mt_stats_lock() {
+    pthread_mutex_lock(&stats_lock);
+}
+
+void mt_stats_unlock() {
+    pthread_mutex_unlock(&stats_lock);
+}
+
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/11/28/YII2-AJAX-Form-Submission.html b/2015/11/28/YII2-AJAX-Form-Submission.html new file mode 100644 index 0000000..04031fc --- /dev/null +++ b/2015/11/28/YII2-AJAX-Form-Submission.html @@ -0,0 +1,1259 @@ + + + +Yii2 AJAX Form Submission - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Yii2 AJAX Form Submission

  + +
+
+ + +

Yii2 provides very nice functionality to create forms and handle client and +server side validation. It creates client side validation rules automatically +from rules provided in Model class. Yii also provides its own Javascript +functionality to validate and submit forms.

+ + + +

Yii triggers many events in Javascript that we can listen to, to modify the +default behavior according to our needs. Following is the list of events that +are currently triggered at different points.

+ +
    +
  • beforeValidate is triggered before validating the whole form
  • +
  • afterValidate is triggered after validating the whole form
  • +
  • beforeValidateAttribute is triggered before validating a single attribute of the form.
  • +
  • afterValidateAttribute is triggered after validating a single attribute of the form.
  • +
  • beforeSubmit is triggered before submitting the form after all validations have passed.
  • +
  • ajaxBeforeSend is triggered before sending an AJAX request for AJAX-based validation.
  • +
  • ajaxComplete is triggered after completing an AJAX request for AJAX-based validation.
  • +
+ +

By default, when the user submits the form, Yii will validate it and submit it +if everything is fine. But sometimes we need to submit the form with AJAX +request instead of a regular HTTP request. We can do this by listening to +beforeSubmit event which is triggered by Yii2 after validation and before +submitting the form.

+ +
$("body").on("beforeSubmit", "form#formId", function () {
+  var form = $(this);
+  // return false if form still have some validation errors
+  if (form.find(".has-error").length) {
+    return false;
+  }
+  // submit form
+  $.ajax({
+    url: form.attr("action"),
+    type: "post",
+    data: form.serialize(),
+    success: function (response) {
+      // do something with response
+    },
+  });
+  return false;
+});
+
+ +

In the above example, we are attaching a beforeSubmit event handler to body +and providing the form#formId selector so that this event is triggered only +for this form. You should replace the #formId with the id of your form.

+ +

Then just to be sure, we are checking if form still have some validation +errors and returning false if it has. And lastly, we are making an actual +Ajax request to the server. In the above example, we are submitting a form to +its default action, but you can submit it to any url you want.

+ +

The important point is that, if we return false from event handler then form +submission will be stopped and Yii will not submit the form.

+ +

Also, When sending a response for an Ajax request in your controller action, +make sure that you have set the response type to JSON, before sending the +response, like following:

+ +
$data = 'Some data to be returned in response to ajax request';
+Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
+return $data;
+
+ +

Yii provides support for different kinds if response types out of the box. You +can read more about these types in +docs

+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/11/28/yii2-pjax.html b/2015/11/28/yii2-pjax.html new file mode 100644 index 0000000..00be0a0 --- /dev/null +++ b/2015/11/28/yii2-pjax.html @@ -0,0 +1,1500 @@ + + + +YII2使用Pjax无刷新加载页面 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

YII2使用Pjax无刷新加载页面

  + +
+
+ + +

pjax is a jQuery plugin that allows quick website navigation by combining ajax +and pushState. It works by sending a special request to the server every time +a link is clicked or a form is submitted. The server sends back the content +that needs to be updated, pjax replaces old content with new content and +pushes the new URL to browser history, without the need to update the whole +page.

+ + + +

regular request and pjax request

+ +
+

Regular requests download the whole page. Pjax can ask the server to send +only the required part of the content.

+
+ +

Yii2 framework has a Pjax widget that helps you pjaxify your website in just +one easy step.

+ +
    +
  1. +

    Add one line in the beginning of your view.

    + +
    <?php
    +use yii\widgets\Pjax;
    +?>
    +
    +
  2. +
  3. +

    Add two lines around the content that needs partial updating.

    + +
    <?php Pjax::begin(); ?>
    +Content that needs to be updated
    +<?php Pjax::end(); ?>
    +
    +
  4. +
+ +

You can further configure the widget. Choose which links and which forms will +be hadled by Pjax; whether the new URL should be pushed to browser history, +replaced or left as is; the timeout after which the page will be reloaded in +case there’s no response; Pjax can also scroll the page for you.

+ +

Yii2 Pjax Examples

+ +

You can download the source code of the examples from this tutorial from +GitHub.

+ +

Refresh

+ +

Let’s look at the simplest example first. We have some data on our page that +we want to refresh by clicking a link. For simplicity’s sake we’ll use the +current server time as our dynamic data. +Demo.

+ +

demo image

+ +

First lets tell the view which part of the page we want to update dynamically. +This is achieved by placing the widgets tags <?php Pjax::begin(); ?> and +<?php Pjax::end(); ?> around the content. Every link and every form between +these tags will now trigger a pjax request.

+ +
<?php Pjax::begin(); ?>
+<?= Html::a("Refresh", ['site/index'], ['class' => 'btn btn-lg btn-primary']) ?>
+<h1>Current time: <?= $time ?></h1>
+<?php Pjax::end(); ?>
+
+ +

Our action only provides the $time to the view and renders the view.

+ +
public function actionIndex()
+{
+    return $this->render('index', ['time' => date('H:i:s')]);
+}
+
+ +

That’s it. Whenever the widget will detect a pjax request it will serve the +required part of the content instead of the whole page.

+ +

Don’t forget to add use yii\widgets\Pjax; in your view.

+ +

Auto refresh

+ +

While we’re on topic of refreshing lets take our previous code and make it +refresh the content automatically after a number of seconds. Demo. To achieve +this we’ll add a couple of lines of javascript to our view.

+ +
<?php
+$script = <<< JS
+$(document).ready(function() {
+    setInterval(function(){
+         $("#refreshButton").click();
+    }, 3000);
+});
+JS;
+
+$this->registerJs($script);
+?>
+
+ +

This code will trigger a click event for our refresh button every three +seconds. Note that we need to specify an id 'id' => 'refreshButton' for our +button for this to work. If you want to you can hide the button by setting its +CSS class to hidden 'class' => 'hidden'.

+ + + +

In this example we will have several links pointing to different controller +actions that will return different results. Our view will look almost the same +as in the previous examples, except now it will have two links/buttons. +Demo.

+ +
<?php Pjax::begin(); ?>
+<?= Html::a("Show Time", ['site/time'], ['class' => 'btn btn-lg btn-primary']) ?>
+<?= Html::a("Show Date", ['site/date'], ['class' => 'btn btn-lg btn-success']) ?>
+<h1>It's: <?= $response ?></h1>
+<?php Pjax::end(); ?>
+
+ +

Here are the two actions that will render the different views. It’s as simple +as that.

+ +
public function actionTime()
+{
+    return $this->render('time-date', ['response' => date('H:i:s')]);
+}
+
+public function actionDate()
+{
+    return $this->render('time-date', ['response' => date('Y-M-d')]);
+}
+
+ +

Multiple blocks

+ +

You can use the widget in several places on one page. When a link inside of +one of these blocks will be clicked it only that block will be updated. +Demo.

+ +

Multiple blocks

+ +

Here is our view. Notice how the widget is used in two places inside one view.

+ +
<div class="col-sm-12 col-md-6">
+    <?php Pjax::begin(); ?>
+    <?= Html::a("Generate Random String", ['site/multiple'], ['class' => 'btn btn-lg btn-primary']) ?>
+    <h3><?= $randomString ?></h3>
+    <?php Pjax::end(); ?>
+</div>
+
+<div class="col-sm-12 col-md-6">
+    <?php Pjax::begin(); ?>
+    <?= Html::a("Generate Random Key", ['site/multiple'], ['class' => 'btn btn-lg btn-primary']) ?>
+    <h3><?= $randomKey ?><h3>
+    <?php Pjax::end(); ?>
+</div>
+
+ +

Here is the corresponding action. We will also require yii\base\Security for +this example.

+ +
public function actionMultiple()
+{
+    $security = new Security();
+    $randomString = $security->generateRandomString();
+    $randomKey = $security->generateRandomKey();
+    return $this->render('multiple', [
+        'randomString' => $randomString,
+        'randomKey' => $randomKey,
+    ]);
+}
+
+ +

This isn’t the best solution. Every time one of the buttons is clicked both, +the random string and the random key, are generated, but we use only one of +them for the purpose of updating the view. To fix this we can add two more +actions and two additional child views, each for the corresponding function +(string generation and key generation). Then we could call the child views +from our main view

+ +
<?= $this->render(_randomKey, [randomKey => $randomKey]); ?>
+
+ +

and render them directly from our additional actions.

+ +

Form submission

+ +

Submitting a form is a great use case for the Pjax widget. For this example we +will be hashing strings so it will take time for the server to respond. +Demo.

+ +

Form submission

+ +

Our view will include the Pjax widget, a form, a text input field and a submit +button.

+ +
<?php Pjax::begin(); ?>
+<?= Html::beginForm(['site/form-submission'], 'post', ['data-pjax' => '', 'class' => 'form-inline']); ?>
+<?= Html::input('text', 'string', Yii::$app->request->post('string'), ['class' => 'form-control']) ?>
+<?= Html::submitButton('Hash String', ['class' => 'btn btn-lg btn-primary', 'name' => 'hash-button']) ?>
+<?= Html::endForm() ?>
+<h3><?= $stringHash ?></h3>
+<?php Pjax::end(); ?>
+
+ +

Here is our action. Nothing out of the ordinary.

+ +
public function actionFormSubmission()
+{
+    $security = new Security();
+    $string = Yii::$app->request->post('string');
+    $stringHash = '';
+    if (!is_null($string)) {
+        $stringHash = $security->generatePasswordHash($string);
+    }
+
+    return $this->render('form-submission', ['stringHash' => $stringHash,]);
+}
+
+ +

Since our forms method is POST, pushState won’t trigger and the URL won’t be +updated.

+ +

Disabling pushState

+ +

Sometimes you will want to disable pushState manually. In this example part of +the view will be updated, but the URL will stay the same. +Demo.

+ +

Disabling pushState

+ +

The view will only have two links and the vote count. To make this simple we +will just store the number of votes in the session. Voting systems are beyond +the scope of this tutorial. To configure the widget we will pass an array as a +parameter in its begin method.

+ +
<?php Pjax::begin(['enablePushState' => false]); ?>
+<?= Html::a('', ['site/upvote'], ['class' => 'btn btn-lg btn-warning glyphicon glyphicon-arrow-up']) ?>
+<?= Html::a('', ['site/downvote'], ['class' => 'btn btn-lg btn-primary glyphicon glyphicon-arrow-down']) ?>
+<h1><?= Yii::$app->session->get('votes', 0) ?></h1>
+<?php Pjax::end(); ?>
+
+ +

The controller will feature three actions. One will simply render the view. +The other two actions will process the upvote and downvote pjax requests.

+ +
public function actionVote()
+{
+    return $this->render('vote');
+}
+
+public function actionUpvote()
+{
+    $votes = Yii::$app->session->get('votes', 0);
+    Yii::$app->session->set('votes', ++$votes);
+    return $this->render('vote');
+}
+
+public function actionDownvote()
+{
+    $votes = Yii::$app->session->get('votes', 0);
+    Yii::$app->session->set('votes', --$votes);
+    return $this->render('vote');
+}
+
+ +

GridView sorting and pagination

+ +

Yii2 GridView has been specifically designed to allow seamless sorting, +filtering and pagination with the Pjax widget. Simply put the GridView widget +inside the Pjax widget in your view. No modifications for the controller are +necessary. +Demo.

+ +

GridView sorting and pagination

+ +
<?php Pjax::begin(); ?>
+<?= GridView::widget([
+    'dataProvider' => $dataProvider,
+    'filterModel' => $searchModel,
+    'columns' => [
+        ['class' => 'yii\grid\SerialColumn'],
+
+        'id',
+        'branch:ntext',
+        'version:ntext',
+        'release_date:ntext',
+
+        [
+            'class' => 'yii\grid\ActionColumn',
+            'template' => '{view}',
+        ],
+    ],
+]); ?>
+<?php Pjax::end(); ?>
+
+ +

Conclusion

+ +

The Pjax widget is great for some use cases. If something goes wrong or the +request will take too much time it can reload the page completely. You can +still open links in new tabs or windows. It’s a great replacement for +CHtml::ajaxLink from Yii 1.x. Search engines don’t have to render JavaScript +to crawl websites that utilize pjax. You can customize it further using +JavaScript.

+ +

It’s not very efficient because it has to send the required data alongside +with HTML code. Depending on your needs, you can make a more efficient web +application using a JavaScript MVC framework(e.g. AngularJS), jQuery or even +plain JavaScript. All you have to do is separate the views from the data, +then, with the help of Ajax, deliver the views as HTML and the data as JSON.

+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/11/30/convert-man-page-to-pdf.html b/2015/11/30/convert-man-page-to-pdf.html new file mode 100644 index 0000000..fd3e138 --- /dev/null +++ b/2015/11/30/convert-man-page-to-pdf.html @@ -0,0 +1,1203 @@ + + + +Convert man page to PDF - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Convert man page to PDF

  + +
+
+ + +

Convert man page to PDF

+ +
man -t bash | ps2pdf - bash.pdf
+
+ + + +

man -t uses groff -mandoc to format the manual page to stdout.

+ +

ps2pdf – bash.pdf means the input is from stdin and output to bash.pdf.

+ +

We use a simple pipe to join the stdout of man and stdin of ps2pdf.

+ +

That’s it!

+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/12/25/jump-function.html b/2015/12/25/jump-function.html new file mode 100644 index 0000000..153b044 --- /dev/null +++ b/2015/12/25/jump-function.html @@ -0,0 +1,1237 @@ + + + +setjmp和longjmp函数使用详解 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

setjmp和longjmp函数使用详解

  + +
+
+ + +
+

转载文章, 不知道原始出处了.

+
+ +

非局部跳转语句setjmplongjmp函数.

+ +

非局部指的是, 这不是由普通 C 语言 goto, 语句在一个函数内实施的跳转, 而是在栈上跳 +过若干调用帧, 返回到当前函数调用路径上的某一个函数中.

+ + + +

函数原型

+ +
#include <setjmp.h>
+
+int setjmp(jmp_buf env);
+int sigsetjmp(sigjmp_buf env, int savesigs);
+
+ +

setjmp()longjmp(3) are useful for dealing with errors and interrupts +encountered in a low-level subroutine of a program. setjmp() saves the +stack context/environment in env for later use by longjmp(3). The stack +context will be invalidated if the function which called setjmp() returns.

+ +

sigsetjmp() is similar to setjmp(). If, and only if, savesigs is nonzero, +the process’s current signal mask is saved in env and will be restored if a +siglongjmp(3) is later performed with this env.

+ +

setjmp() and sigsetjmp() return 0 if returning directly, and nonzero when +returning from longjmp(3) or siglongjmp(3) using the saved context.

+ +
#include <setjmp.h>
+
+void longjmp(jmp_buf env, int val);
+void siglongjmp(sigjmp_buf env, int val);
+
+ +

longjmp() and setjmp(3) are useful for dealing with errors and +interrupts encountered in a low-level subroutine of a program. longjmp() +restores the environment saved by the last call of setjmp(3) with the +corresponding env argument. After longjmp() is completed, program execution +continues as if the corresponding call of setjmp(3) had just returned the +value val. longjmp() cannot cause 0 to be returned. If longjmp() is +invoked with a second argument of 0, 1 will be returned instead.

+ +

siglongjmp() is similar to longjmp() except for the type of its env +argument. If, and only if, the sigsetjmp(3) call that set this env used a +nonzero savesigs flag, siglongjmp() also restores the signal mask that +was saved by sigsetjmp(3).

+ +

These functions never return.

+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/12/26/graphviz-dot.html b/2015/12/26/graphviz-dot.html new file mode 100644 index 0000000..53e8aac --- /dev/null +++ b/2015/12/26/graphviz-dot.html @@ -0,0 +1,1401 @@ + + + +利用Graphviz Dot绘图 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

利用Graphviz Dot绘图

  + +
+
+ + +
+

转自: http://www.cnblogs.com/sld666666/archive/2010/06/25/1765510.html

+
+ +

Graphviz 介绍

+ +

Graphviz 是大名鼎鼎的贝尔实验室的几位牛人开发的一个画图工具. 它的理念和一般 +的”所见即所得”的画图工具不一样, 是”所想即所得”. Graphviz 提供了 dot 语言来编写绘 +图脚本. 什么?! 画个图也需要一个语言!! 不要急, dot 语言是非常简单地, 只要看了下 +面几个例子, 就能使用了.

+ + + +

Graphviz 的几个例子

+ +

Fancy graph

+ +
digraph G {
+
+    // 图片大小
+    size = "4, 4";
+    // 形状
+    main[shape=box];
+
+    main->parse;
+    parse->execute;
+
+    // 虚线
+    main->init[style = dotted];
+
+    main->cleanup;
+
+    // 连接两个
+    execute->{make_string; printf}
+
+    init->make_string;
+
+    // 连接线的颜色
+    edge[color = red];
+
+    // 线的 label
+    main->printf[style=bold, label="100 times"];
+
+    // \n, 这个node的label,注意和上一行的区别
+    make_string[label = "make a\nstring"]
+
+    // 一个node的属性
+    node[shape = box, style = filled, color = ".7.3 1.0"];
+
+    execute->compare;
+}
+
+ +

从上面的代码可以看出, dot 语言非常简单, 就是一个纯描述性的语言而已. 大家可以把 +上面的代码和下图中的连接对应起来看.

+ +

+ +

Polygon graph

+ +
digraph G{
+    size = "4, 4"
+    a->b->c;
+    b->d;
+
+    a[shape = polygon, sides = 5, peripheries=3, color = lightblue, style = filled];
+    //我的形状是多边形,有五条边,3条边框, 颜色的淡蓝色, 样式为填充
+    c[shape = polygon, sides = 4, skew= 0.4, lable = "hello world"];
+    //我的形状是4变形, 角的弯曲度0.4, 里面的内容为"hello world"
+    d[shape = invtriange];
+    //我是三角形
+    e[shape = polygon, side = 4, distortion = .7];
+    //我是梯形啊
+}
+
+ +

下面是对应的图片:

+ +

+ +

连接点的方向

+ +

我们可以用”n”, “ne”, “e”, ““se”, “sw”, “w”, “nw”, 分别表示冲哪一个方向连接这 +个节点(图形) — “north, northeast…”

+ +
digraph G{
+    //b->c[tailport = se];
+    b->c:se;
+}
+
+ +

效果如下:

+ +

+ +

数据结构图

+ +

数据结构图是我们很容易用到的一类图形, 一个简单地数据结构图代码如下:

+ +
digraph g{
+    node [shape = record, height=.1//我定义了我下面的样式;
+    node0[label = "<f0> |<f1> G|<f2> "];
+    //我是一个node, 我有三个属性, 第二个的名字为G, 其他两个为空
+    node1[label = "<f0> |<f1> E|<f2> "];
+    node2[label = "<f0> |<f1> B|<f2> "];
+    node3[label = "<f0> |<f1> F|<f2> "];
+    node4[label = "<f0> |<f1> R|<f2> "];
+    node5[label = "<f0> |<f1> H|<f2> "];
+    node6[label = "<f0> |<f1> Y|<f2> "];
+    node7[label = "<f0> |<f1> A|<f2> "];
+    node8[label = "<f0> |<f1> C|<f2> "];
+
+    "node0": f2->"node4":f1;
+    //我的第三个属性连到node4的第二个属性
+    "node0": f0->"node1":f1;
+    "node1": f0->"node2":f1;
+    "node1": f2->"node3":f1;
+    "node2": f2->"node8":f1;
+    "node2": f0->"node7":f1;
+    "node4": f2->"node6":f1;
+    "node4": f0->"node5":f1;
+}
+
+ +

效果如下:

+ +

+ +

Hash table graph

+ +
digraph g {
+    nodesep = .05;
+    rankdir = LR;
+
+    node[shape = record,  width = .1,  height = .1];
+
+    node0[label = "<f0> |<f1> |<f2> |<f3> |<f4> |<f5> |<f6> |",  height = 2.5];
+    //我是一个节点, 我有7个属性
+    node [width = 1.5];
+    node1[label = "{<n> n14 | 719 |<p>}"];
+    //我还是一个节点,  也定义了三个属性
+    node2[label = "{<n> a1 | 719 |<p>}"];
+    node3[label = "{<n> i9 | 512 |<p>}"];
+    node4[label = "{<n> e5 | 632 |<p>}"];
+    node5[label = "{<n> t20 | 959 |<p>}"];
+    node6[label = "{<n> o15 | 794 |<p>}"];
+    node7[label = "{<n> s19 | 659 |<p>}"];
+
+    //好了, 我开始连接了
+    node0:f0->node1:n;
+    node0:f1->node2:n;
+    node0:f2->node3:n;
+    node0:f5->node4:n;
+    node0:f6->node5:n;
+    node2:p->node6:n;
+    node4:p->node7:n;
+}
+
+ +

效果如下:

+ +

+ +

这是一个简单地哈希表, 如下图所示

+ +

Process grahp

+ +

下面画一个轻量级的流程图.

+ +
digraph g {
+    subgraph cluster0 {
+        //我是一个子图, subgraph定义了我,
+        node[style = filled,  color = white];
+        //我之内的节点都是这种样式
+        style = filled;
+        //我的样式是填充
+        color = lightgrey;
+        //我的颜色
+        a0->a1->a2->a3;
+        label = "prcess #1"
+        //我的标题
+    }
+
+    subgraph cluster1 {
+        //我也是一个子图
+        node[style = filled];
+        b0->b1->b2->b3;
+        label = "process #2";
+        color = blue;
+    }
+
+    //定义完毕之后, 下面还是连接了
+    start->a0;
+    start->b0;
+    a1->b3;
+    b2->a3;
+    a3->end;
+    b3->end;
+
+    start[shape=Mdiamond];
+    end[shape=Msquare];
+}
+
+ +

结果输出图形如下:

+ +

+ +

小结

+ +

相信这几个列子下来, 各位看官对 graphviz 也有了了解了吧, 我个人用了一遍下来发现太爽了. +而对于 dot 语言, 作为一个描述性的语言就非常简单了, 只要有编程基础的人, 模仿几个列子下来 +应该就能应用了.

+ +

各位看官, 有没有心动啊.

+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/12/26/python-grammer.html b/2015/12/26/python-grammer.html new file mode 100644 index 0000000..121c065 --- /dev/null +++ b/2015/12/26/python-grammer.html @@ -0,0 +1,1525 @@ + + + +Python解释器的部分数据结构 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Python解释器的部分数据结构

  + +
+
+ + +

刚刚学习了一下 dot 语言绘制图像, 现在来画几张数据结构图, 牛刀小试一下.

+ + + +

源码:

+ +
typedef struct _label {
+       int     lb_type;
+       char    *lb_str;
+} label;
+
+typedef struct _labellist {
+       int     ll_nlabels;
+       label   *ll_label;
+} labellist;
+
+typedef struct _arc {
+       short           a_lbl;          /* Label of this arc */
+       short           a_arrow;        /* State where this arc goes to */
+} arc;
+
+typedef struct _state {
+       int              s_narcs;
+       arc             *s_arc;         /* Array of arcs */
+
+       /* Optional accelerators */
+       int              s_lower;       /* Lowest label index */
+       int              s_upper;       /* Highest label index */
+       int             *s_accel;       /* Accelerator */
+       int              s_accept;      /* Nonzero for accepting state */
+} state;
+
+typedef struct _dfa {
+       int              d_type;        /* Non-terminal this represents */
+       char            *d_name;        /* For printing */
+       int              d_initial;     /* Initial state */
+       int              d_nstates;
+       state           *d_state;       /* Array of states */
+       bitset           d_first;
+} dfa;
+
+typedef struct _grammar {
+       int              g_ndfas;
+       dfa             *g_dfa;         /* Array of DFAs */
+       labellist        g_ll;
+       int              g_start;       /* Start symbol of the grammar */
+       int              g_accel;       /* Set if accelerators present */
+} grammar;
+
+ +

dot 可以这样写:

+ +
digraph grammer {
+    node[shape=record];
+    rankdir = LR;
+
+    grammer[label = "
+        <g_ndfas> g_ndfas |
+        <g_dfa> *g_dfa |
+        <g_ll> g_ll |
+        <g_start> g_start |
+        <g_accel> g_accel
+    "];
+
+    dfa[label = "
+        <d_type> d_type |
+        <d_name> *d_name |
+        <d_initial> d_initial |
+        <d_nstates> d_nstates |
+        <d_state> *d_state |
+        <d_first> d_first
+    "];
+
+    state[label="
+        <s_narcs> s_narcs |
+        <s_arc> *s_arc |
+        <s_lower> s_lower |
+        <s_upper> s_upper |
+        <s_accel> *s_accel |
+        <s_accept> s_accept
+    "];
+
+    arc[label="
+        <a_lbl> a_lbl |
+        <a_arrow> a_arrow
+    "];
+
+    labellist[label="
+        <ll_nlabels> ll_nlabels |
+        <ll_label> *ll_label
+    "];
+
+    label[label="
+        <lb_type> lb_type |
+        <lb_str> *lb_str
+    "];
+
+    // 现在把结构体关联起来
+    grammer:g_dfa->dfa:n;
+    grammer:g_ll->labellist:n;
+
+    dfa:d_state->state:n;
+    state:s_arc->arc:n;
+
+    labellist:ll_label->label:n;
+}
+
+ +

效果如下:

+ +
+ + + + +grammer +digraph grammer { + node[shape=record]; + rankdir = LR; + ranksep = 0.8; + + grammer[label = " + <g_ndfas> g_ndfas | + <g_dfa> *g_dfa | + <g_ll> g_ll | + <g_start> g_start | + <g_accel> g_accel + ", xlabel = "grammer"]; + + dfa[label = " + <d_type> d_type | + <d_name> *d_name | + <d_initial> d_initial | + <d_nstates> d_nstates | + <d_state> *d_state | + <d_first> d_first + ", xlabel = "dfa"]; + + state[label=" + <s_narcs> s_narcs | + <s_arc> *s_arc | + <s_lower> s_lower | + <s_upper> s_upper | + <s_accel> *s_accel | + <s_accept> s_accept + ", xlabel = "state"]; + + arc[label=" + <a_lbl> a_lbl | + <a_arrow> a_arrow + ", xlabel = "arc"]; + + labellist[label=" + <ll_nlabels> ll_nlabels | + <ll_label> *ll_label + ", xlabel = "labellist"]; + + label[label=" + <lb_type> lb_type | + <lb_str> *lb_str + ", xlabel = "label"]; + + // tok_state + tok_state[label=" + <buf> *buf | + <cur> *cur | + <inp> *inp | + <end> *end | + <done> done | + <fp> *fp | + <tabsize> tabsize | + <indent> indent | + <indstack> *indstack | + <atbol> atbol | + <pendin> pendin | + <prompt> *prompt | + <nextprompt> *nextprompt | + <lineno> lineno + ", xlabel = "tok_state"]; + + // 现在把结构体关联起来 + grammer:g_dfa->dfa:n; + grammer:g_ll->labellist:n; + + dfa:d_state->state:n; + state:s_arc->arc:n; + + labellist:ll_label->label:n; +} + + +grammer + + + +grammer + +g_ndfas + +*g_dfa + +g_ll + +g_start + +g_accel +grammer + + + +dfa + +d_type + +*d_name + +d_initial + +d_nstates + +*d_state + +d_first +dfa + + + +grammer:g_dfa->dfa:n + + + + + +labellist + +ll_nlabels + +*ll_label +labellist + + + +grammer:g_ll->labellist:n + + + + + +state + +s_narcs + +*s_arc + +s_lower + +s_upper + +*s_accel + +s_accept +state + + + +dfa:d_state->state:n + + + + + +arc + +a_lbl + +a_arrow +arc + + + +state:s_arc->arc:n + + + + + +label + +lb_type + +*lb_str +label + + + +labellist:ll_label->label:n + + + + + +tok_state + +*buf + +*cur + +*inp + +*end + +done + +*fp + +tabsize + +indent + +*indstack + +atbol + +pendin + +*prompt + +*nextprompt + +lineno +tok_state + + + +
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/12/30/yii2-getting-start.html b/2015/12/30/yii2-getting-start.html new file mode 100644 index 0000000..5e389a6 --- /dev/null +++ b/2015/12/30/yii2-getting-start.html @@ -0,0 +1,1242 @@ + + + +Yii2学习笔记 -- 开始学习 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Yii2学习笔记 -- 开始学习

  + +
+
+ + +

可以使用 composer 来安装 yii2 的官方基本模板或者高级模板. 末班里面带了一些实例, 可 +以作为入门学习.

+ +

使用基本模板

+ +
composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic
+
+ +

上面的 shell 语句会帮助我们自动的安装 yii2 框架, 并处理依赖关系.

+ + + +

下一步即为配置服务器, 把服务器的 Document Root 设置为 basic/web 目录, 之后, 使用 +浏览器访问http://localhost/index.php即可看到框架已经可以使用了.

+ +

Yii2 还为我们准备了一个三层测试框架, 用来执行验收测试(acceptice tests), 功能测 +试(functional tests)以及单元测试(unit tests). 应用模板里面的测试代码很好的展 +示了如何使用测试框架(Codeception).

+ +

基本模板有个缺点, 它不支持模块, 比如像后端管理. 这导致开发大型的项目会比较困难.

+ +

通过查看composer.json文件, 我们会发现, 基本模板包含了一些重要的可插拔包. +比如像 Gii, Codeception, SwiftMailer, BootstrapUi 等等.

+ +

使用高级模板

+ +

Yii2 提供了高级应用模板, 适用于创建一些稍微复杂点的项目. 它最主要的特性是, 分为 +两个独立的 web 模块. 一个用作 CMS, 另外一个用来向客户展示内容.

+ +

安装高级模板和上面安装基本模板差不多, 把 basic 替换成 advanced 就可以了:

+ +
create-project --prefer-dist --stability=dev yiisoft/yii2-app-advanced advanced
+
+ +

安装好高级模板之后, 我们需要初始化一些数据. 可以执行根目录下的init命令(脚本) +来初始化数据. 脚本背后的逻辑很简单, 问你点问题, 然后从dev或者prod目录拷贝模 +板出来.

+ +

接下来是创建高级应用模板使用的数据库, 默认是 yii2advanced 数据库. 这个配置在 +common/config/main-local.php文件, 你可以打开看看. 数据库完成之后, 就是迁移 +数据, 这一步通过执行migrate命令来执行:

+ +
./yii migrate/up
+
+ +

到此, 高级应用模板就安装完成了. 下面设置下 web 服务器, 使得两个 virtual host 根目 +录分别指向frontend/webbackend/web目录. backend 即上文说的 CMS 系统, frontend +是用于展示的模块.

+ +

之后访问http://frontend.domain.comhttp://backend.domain.com即可看到效果.

+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2015/12/30/yii2-making-custom-app-with-yii2.html b/2015/12/30/yii2-making-custom-app-with-yii2.html new file mode 100644 index 0000000..35e102a --- /dev/null +++ b/2015/12/30/yii2-making-custom-app-with-yii2.html @@ -0,0 +1,1251 @@ + + + +Yii2学习笔记 -- 构建一个自定义的应用 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Yii2学习笔记 -- 构建一个自定义的应用

  + +
+
+ + +

这次, 我们来实现一个小小的 web 应用. 我们将用绝对符合软件工程的方法来实现.

+ +

设计阶段

+ +

我们的任务

+ +

假想我们是一个提供某种服务的小商户. 我们的客户数量很多, 所以, 用纸张来存储客户 +信息会显得有点笨重. 所以, 我们需要一种能接受给定信息, 反馈出客户全部信息的自动 +查询方法.

+ + + +

领域模型设计

+ +

我们使用customer一词来表示客户. 一个客户的信息应该包含名字, 电话, 地址, 电邮 +等, 我们对客户收费按照合同里面规定的服务小时数来计算. 我们在第一个迭代里面将会 +包含这些内容.

+ +

我们假设是每个客户就是一个单独的人. 这样我们就不用处理公司这种可能有多个联系人 +的情况. 如果我们把客户的名字分解成姓,名,昵称…那将会是一个颇为复杂的结构. 索性 +我们就不考虑这么多, 反正对我们来说, 名字就是一个标示符而已. 那么, 我们就把它设置 +成一行文本算逑, 怎么写也随你便. 细想起来, 地址也够复杂, 但是我们不能简单地用文 +本代替, 因为我们需要对地址做以下处理:

+ +
    +
  • 计算一些统计信息, 比如某个城市有多少用户.
  • +
  • 根据不同的文化类型, 生成对应的邮政地址
  • +
+ +

所以, 我们决定把地址定义成如下的数据结构:

+ +
    +
  • 用途(比如工作地址, 邮寄地址, 家庭地址等)
  • +
  • 国家
  • +
  • 州/省
  • +
  • +
  • 街道
  • +
  • XX 大楼
  • +
  • xx 办公室/室
  • +
  • 收件人
  • +
  • 邮编
  • +
+ +

我们要注意到, 一个地址可以是一个公寓, 一个邮箱, 一个办公室, 或者是一个组织的一 +名雇员. 当然一个人也可以有多个地址.

+ +

同理, 由于一个人可以有多个号码, 所以电话设计成下面的结构:

+ +
    +
  • 用途
  • +
  • 号码
  • +
+ +

除了名字, 地址, 电话之外. 很明显我们还需要一个文本字段来表述客户, 我们把这个叫做 +备注(notes). 嗯, 记下来用户的生日也很酷, 还有电邮. 顺便说下, 电邮也可以有多个

+ +

先停下, 我们根据上面的讨论, 绘制了如下的 ER 图:

+ +

图没有画

+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2016/01/06/reactjs-tutorial.html b/2016/01/06/reactjs-tutorial.html new file mode 100644 index 0000000..6abd4db --- /dev/null +++ b/2016/01/06/reactjs-tutorial.html @@ -0,0 +1,1996 @@ + + + +React基本教程 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

React基本教程

  + +
+
+ + +

目标

+ +

我们将创建一个小但是很实用的评论框.

+ +

功能如下:

+ +
    +
  1. 查看所有的评论
  2. +
  3. 提交评论的表单
  4. +
  5. 实现自定义后端的钩子
  6. +
+ + + +

我们的程序有如下特点:

+ +
    +
  • 优化评论: 在评论被送到服务器之前就会显示在视图里面了, 这会让人感觉它很快.
  • +
  • 实更新行: 其他用户的评论将会实时出现在评论列表里面
  • +
  • Markdown 支持: 支持使用 markdown 来格式化我们输入的文本
  • +
+ +

关于服务器

+ +

为了实现这个教程, 我们需要一个 web 服务器. 这个服务器作为 API 提供者让我们能获取/存 +储数据.

+ +

问了简单起见, 服务器使用一个 JSON 文件作为数据库. 生产环境我们不会这么用, 但是在 +模拟 API 的时候, 这种方法确实很简单. 一旦你启动了服务器, 它就会作为 API 使用. 它也 +会为我们提供静态文件服务.

+ +

开始

+ +

这个教程将尽可能的简单. 下载这个, 用你喜欢的编辑器打开 +public/index.html, 它看上去应该像这样(原文从 CDN 里面取 JS 文件, 这里本地化了):

+ +
<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8" />
+    <title>React Tutorial</title>
+    <!-- Not present in the tutorial. Just for basic styling. -->
+    <link rel="stylesheet" href="css/base.css" />
+    <script src="../vendor/js/react.js"></script>
+    <script src="../vendor/js/react-dom.js"></script>
+    <script src="../vendor/js/browser.js"></script>
+    <script src="../vendor/js/jquery.min.js"></script>
+    <script src="../vendor/js/marked.min.js"></script>
+  </head>
+  <body>
+    <div id="content"></div>
+    <script type="text/babel" src="scripts/example.js"></script>
+    <script type="text/babel">
+      // To get started with this tutorial running your own code, simply remove
+      // the script tag loading scripts/example.js and start writing code here.
+    </script>
+  </body>
+</html>
+
+ +

在文章剩余的部分中, 我们会在这个 script 标签里面写代码. 我们没有自动刷新的工具, +因此, 你需要手动刷新, 来查看效果. 当你第一次打开浏览器的时候, 可以看到我们要做 +出来的最终效果, 在开始继续之前, 把第一个<script>标签删掉(它用来演示).

+ +
+

注意: +我们包含了 JQuery 库, 但是 React 并不需要它. 我们只是为了使用 AJAX 方便才引入它的.

+
+ +

第一个组件

+ +

React 是由模块以及组件组成的, 对我们的评论例子来说, 我们将使用如下的组成结构:

+ +
    +
  • CommentBox +
      +
    • CommentList +
        +
      • Comment
      • +
      +
    • +
    • CommentForm
    • +
    +
  • +
+ +

下面, 我们先来创建一个 CommentBox 组件, 它本质上是一个<div>标签.

+ +
// tutorial1.js
+var CommentBox = React.createClass({
+  render: function () {
+    return <div className="commentBox">Hello, world! I am a CommentBox.</div>;
+  },
+});
+ReactDOM.render(<CommentBox />, document.getElementById("content"));
+
+ +

注意, 原生的 HTML 标签都是小写字母打头的. 而自定义的 React 类名称是以大写字母打头 +的.

+ +

JSX 语法

+ +

你可能注意到了 JavaScript 代码里面掺杂的 XML 格式代码. 我们有一个简单地预编译器来 +处理这种语法糖(把它们转换成标准的 JavaScript 代码).

+ +

转换后的代码如下:

+ +
// tutorial1-raw.js
+var CommentBox = React.createClass({
+  displayName: "CommentBox",
+  render: function () {
+    return React.createElement(
+      "div",
+      { className: "commentBox" },
+      "Hello, world! I am a CommentBox."
+    );
+  },
+});
+ReactDOM.render(
+  React.createElement(CommentBox, null),
+  document.getElementById("content")
+);
+
+ +

JSX 的使用不是强制的, 但是很明显, 这种语法与原生 JavaScript 相比会让我们省很多事.

+ +

发生了什么

+ +

我们通过 JavaScript 对象传递一些方法到React.createClass()来创建一个新的 React +组件. 其中最重要的方法是render, 该方法返回一棵React组件树, 这棵树最终将会 +渲染成 HTML.

+ +

这里的<div>标签不是真正的 DOM 节点;他们是React div组件的实例. 你可以认为这 +些标签就是一些标记或者数据, React 知道如何处理它们. React 是安全的, 我们不生成 +HTML 字符串, 因此默认阻止了 XSS 攻击.

+ +

没必要返回基本的 HTML 结构, 可以返回一个你(或者其他人)创建的组件树. 而这就是让 +React 变得组件化的特性: 一个前端可维护性的关键原则.

+ +

ReactDOM.render()实例化根组件, 启动框架, 把标记注入到第二个参数指定的原生的 +DOM 元素中.

+ +

ReactDOM 模块提供了一些 DOM 相关的方法, 而 React 模块包含了 React 团队分享的不同平台 +上的核心工具(例如, React Native).

+ +

本教程中, 把ReactDOM.render放到脚本的最后是很重要的, 这个方法应该在组件定义 +之后再调用.

+ +

创建组件

+ +

让我们再次使用简单地<div>来创建 CommentList 和 CommentForm 组件. 把这两个组件加 +入到你的文件, 注意保留 CommentBox 组件和ReactDOM.render调用

+ +
// tutorial2.js
+var CommentList = React.createClass({
+  render: function () {
+    return <div className="commentList">Hello, world! I am a CommentList.</div>;
+  },
+});
+
+var CommentForm = React.createClass({
+  render: function () {
+    return <div className="commentForm">Hello, world! I am a CommentForm.</div>;
+  },
+});
+
+ +

接下来, 请把 CommentBox 组件更新成下面这样:

+ +
// tutorial3.js
+var CommentBox = React.createClass({
+  render: function () {
+    return (
+      <div className="commentBox">
+        <h1>Comments</h1>
+        <CommentList />
+        <CommentForm />
+      </div>
+    );
+  },
+});
+
+ +

注意这里我们是如何把 HTML 标签和组件组合到一起的. HTML 组件就是普通的 React 组件, +就和你定义的组件一样, 只不过有一处不同. JSX 编译器会自动重写 HTML 标签为 +React.createElement(tagName)表达式, 其它什么都不做. 这是为了避免全局命名空间 +污染.

+ +

使用属性

+ +

接下来创建 Comment 组件, 它依赖于它的父级元素传递给它的数据. 父级元素传过来的数据 +作为子元素的’属性’存在. 这些属性可以通过this.props来访问. 通过使用属性, 我们 +将能读取由 CommentList 传递给 Comment 的数据, 然后执行渲染.

+ +
// tutorial4.js
+var Comment = React.createClass({
+  render: function () {
+    return (
+      <div className="comment">
+        <h2 className="commentAuthor">{this.props.author}</h2>
+        {this.props.children}
+      </div>
+    );
+  },
+});
+
+ +

在 JSX 里面, 通过使用大括号包住一个 JavaScript 表达式(比如作为属性或者作为子节点), +你就可以把文字或者 React 组件放到 DOM 树中. 我们通过this.props来访问传入组件的数 +据, 键名就是对应的命名属性, 也可以通过this.props.children访问组件内嵌的任何 +元素.

+ +

组件属性

+ +

现在我们已经定义好了 Comment 组件, 我们想传递给它作者名字和评论文本, 以便于我们 +能够对每一个独立的评论重用相同的代码. 首先让我们添加一些评论到 CommentList:

+ +
// tutorial5.js
+var CommentList = React.createClass({
+  render: function () {
+    return (
+      <div className="commentList">
+        <Comment author="Pete Hunt">This is one comment</Comment>
+        <Comment author="Jordan Walke">This is *another* comment</Comment>
+      </div>
+    );
+  },
+});
+
+ +

请注意, 我们从父级元素(CommentList)传递了一些数据给子级元素. 比如说, 我们通过 +属性传递了 Peter Hunt 以及This is one comment(这个就是是: this.props.chidren) +正如前面说的那样, Comment 组件通过this.props.authorthis.props.children来 +访问这些”属性”.

+ +

添加 Markdown 支持

+ +

本教程使用第三方库来完成由 Markdown 到 HTML 的转换. 我们在原始的标签页面已经包含了 +它, 现在直接用就可以了.

+ +
// tutorial6.js
+var Comment = React.createClass({
+  render: function () {
+    return (
+      <div className="comment">
+        <h2 className="commentAuthor">{this.props.author}</h2>
+        {marked(this.props.children.toString())}
+      </div>
+    );
+  },
+});
+
+ +

这里我们唯一需要做的就是调用 marked 库. 我们需要把this.props.children从 React 的 +包裹文本转换成marked能处理的原始的字符串, 所以我们显式地调用了toString().

+ +

但是这里有一个问题! 我们渲染的评论内容在浏览器里面看起来像这样: +<p>This is <em>another</em> comment</p>. 我们希望这些标签能够真正地渲染成 HTML.

+ +

这是React在保护你免受XSS攻击. 这里有一种方法解决这个问题, 但是框架会警告你 +别使用这种方法:

+ +
// tutorial7.js
+var Comment = React.createClass({
+  rawMarkup: function () {
+    var rawMarkup = marked(this.props.children.toString(), { sanitize: true });
+    return { __html: rawMarkup };
+  },
+
+  render: function () {
+    return (
+      <div className="comment">
+        <h2 className="commentAuthor">{this.props.author}</h2>
+        <span dangerouslySetInnerHTML={this.rawMarkup()} />
+      </div>
+    );
+  },
+});
+
+ +

这是一个特别的 API, 故意让插入原始的 HTML 代码显得有点困难. 但是对于 marked, 我们 +将会利用这个后门.

+ +

记住: 使用这个特性, 你的代码就要依赖于 marked 的安全性. 在本场景中, 我们传入 +sanitize: true, 告诉 marked 转义掉评论文本中的 HTML 标签而不是直接原封不动地返回 +这些标签.

+ +

接入数据模型

+ +

到米钱位置, 我们都是直接在源码里面插入数据的. 现在让我们从一个大的 JSON 里面来渲 +染列表. 最终的 JSON 数据将会从服务器请求, 现在我们先把他写在代码里面.

+ +
// tutorial8.js
+var data = [
+  { id: 1, author: "Pete Hunt", text: "This is one comment" },
+  { id: 2, author: "Jordan Walke", text: "This is *another* comment" },
+];
+
+ +

我们以模块的形式将数据插入到 CommentList, 下面修改 CommentBox 以及 +ReactDOM.render()调用, 并把这个数据通过属性传递到 CommentList.

+ +
// tutorial9.js
+var CommentBox = React.createClass({
+  render: function () {
+    return (
+      <div className="commentBox">
+        <h1>Comments</h1>
+        <CommentList data={this.props.data} />
+        <CommentForm />
+      </div>
+    );
+  },
+});
+
+ReactDOM.render(<CommentBox data={data} />, document.getElementById("content"));
+
+ +

现在, 在 CommentList 里面, data 就是可用的了, 下面我们来动态的渲染评论.

+ +
// tutorial10.js
+var CommentList = React.createClass({
+  render: function () {
+    var commentNodes = this.props.data.map(function (comment) {
+      return (
+        <Comment author={comment.author} key={comment.id}>
+          {comment.text}
+        </Comment>
+      );
+    });
+    return <div className="commentList">{commentNodes}</div>;
+  },
+});
+
+ +

差不多就这样了.

+ +

从服务器获取数据

+ +

我们下面用动态从服务器获取的数据换掉硬编码在代码里面的数据. 我们将会移除data +属性并且使用一个 URL 来替换它.

+ +
// tutorial11.js
+ReactDOM.render(
+  <CommentBox url="/api/comments" />,
+  document.getElementById("content")
+);
+
+ +

这个组件和我们之前见到的组件有一点不同, 因为待会它将会渲染它自身. 组件在服务器 +返回数据之前, 不会有任何数据. 请求返回之后, 这个组件可能需要渲染一些新的评论.

+ +
+

注意 这些代码目前无法工作

+
+ +

响应状态变化

+ +

目前为止, 每个组件依赖于它的属性渲染自己一次. 属性是不可变的: 它们从父组件传递 +过来, “属于”父组件. 为了实现交互, 我们给组件引入了可变的 state. this.state是 +组件私有的, 可以通过调用this.setState()来改变它. 当 state 更新之后, 组件就会重 +新渲染自己.

+ +

render()方法依赖于this.propsthis.state, 框架会确保渲染出来的 UI 界面总是 +与输入(this.propsthis.state)保持一致.

+ +

当服务器拿到评论数据的时候, 我们将会用已知的评论数据改变评论. 让我们给 +CommentBox 组件添加一个评论数组作为它的 state:

+ +
// tutorial12.js
+var CommentBox = React.createClass({
+  getInitialState: function () {
+    return { data: [] };
+  },
+  render: function () {
+    return (
+      <div className="commentBox">
+        <h1>Comments</h1>
+        <CommentList data={this.state.data} />
+        <CommentForm />
+      </div>
+    );
+  },
+});
+
+ +

getInitialState()在组件的生命周期里面只执行一次, 用于初始化组件的 state.

+ +

更新状态

+ +

当组件第一次被创建的时候, 我们希望通过 GET 方法从服务器获得一些 JSON 数据, 然后 +更新 state, 并展示数据. 我们将会使用 jQuery 发送一个异步请求到我们之前启动好的服 +务器, 获取我们需要的数据. 在我们启动服务器的时候, 数据就已经准备好了(放在 +comments.json 文件里面), 数据格式和相应代码看起来会是这个样子:

+ +
[
+  { author: "Pete Hunt", text: "This is one comment" },
+  { author: "Jordan Walke", text: "This is *another* comment" },
+];
+
+ +
// tutorial13.js
+var CommentBox = React.createClass({
+  getInitialState: function () {
+    return { data: [] };
+  },
+  componentDidMount: function () {
+    $.ajax({
+      url: this.props.url,
+      dataType: "json",
+      cache: false,
+      success: function (data) {
+        this.setState({ data: data });
+      }.bind(this),
+      error: function (xhr, status, err) {
+        console.error(this.props.url, status, err.toString());
+      }.bind(this),
+    });
+  },
+  render: function () {
+    return (
+      <div className="commentBox">
+        <h1>Comments</h1>
+        <CommentList data={this.state.data} />
+        <CommentForm />
+      </div>
+    );
+  },
+});
+
+ +

这里, componentDidMount是一个在组件第一次加载完成后由 React 自动调用的方法, +这里动态更新的关键是调用this.setState()方法. 我们使用从服务器得到的新的数组 +来代替原来的空数组, UI 会随之自动更新. 有了这种反应机制, 实现实时更新就仅需要一 +小点改动. 在这里我们使用简单的轮询, 但是你也可以很容易地改为使用 WebSockets 或者 +其他技术.

+ +
// tutorial14.js
+var CommentBox = React.createClass({
+  loadCommentsFromServer: function () {
+    $.ajax({
+      url: this.props.url,
+      dataType: "json",
+      cache: false,
+      success: function (data) {
+        this.setState({ data: data });
+      }.bind(this),
+      error: function (xhr, status, err) {
+        console.error(this.props.url, status, err.toString());
+      }.bind(this),
+    });
+  },
+  getInitialState: function () {
+    return { data: [] };
+  },
+  componentDidMount: function () {
+    this.loadCommentsFromServer();
+    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
+  },
+  render: function () {
+    return (
+      <div className="commentBox">
+        <h1>Comments</h1>
+        <CommentList data={this.state.data} />
+        <CommentForm />
+      </div>
+    );
+  },
+});
+
+ReactDOM.render(
+  <CommentBox url="/api/comments" pollInterval={2000} />,
+  document.getElementById("content")
+);
+
+ +

我们在这里, 把 AJAX 请求移到了一个单独的方法里面, 然后在组件第一次加载的时候调用. +此后, 每隔 2 秒轮询一次. 你可以试着在你自己的浏览器里面跑一跑这个程序, 改一改 +comments.json, 两秒之后就能看到更新.

+ +

添加新评论

+ +

现在, 是时候构建一个表单了. 我们的 CommentForm 组件应该向用户询问他的名字以及评 +论内容, 然后向服务器发送一个请求, 来保存评论.

+ +
// tutorial15.js
+var CommentForm = React.createClass({
+  render: function () {
+    return (
+      <form className="commentForm">
+        <input type="text" placeholder="Your name" />
+        <input type="text" placeholder="Say something..." />
+        <input type="submit" value="Post" />
+      </form>
+    );
+  },
+});
+
+ +

controlled 组件

+ +

使用传统的 DOM, 由浏览器执行输入元素的渲染以及状态(即渲染结果)的维护, 结果就是 +真实 DOM 会和组件有所不同. 因为视图的状态会和组件不同, 所以这并不理想. 在 React 里 +面, 组件一直是代表视图的状态的, 而并不仅仅是在初始化的时候.

+ +

因此, 我们可以使用this.state来存储用户的输入, 我们初始化两个属性, 即author +和text并把他们设置为空字符串. 在我们的<input>元素里面, 我们设定属性值来关联 +到组件的状态 并且把onChange事件处理函数与之关联. 这种有一个设定值的<input> +标签叫做controlled组件, 你可以在 Form 章节查到更多信息.

+ +
// tutorial16.js
+var CommentForm = React.createClass({
+  getInitialState: function () {
+    return { author: "", text: "" };
+  },
+  handleAuthorChange: function (e) {
+    this.setState({ author: e.target.value });
+  },
+  handleTextChange: function (e) {
+    this.setState({ text: e.target.value });
+  },
+  render: function () {
+    return (
+      <form className="commentForm">
+        <input
+          type="text"
+          placeholder="Your name"
+          value={this.state.author}
+          onChange={this.handleAuthorChange}
+        />
+        <input
+          type="text"
+          placeholder="Say something..."
+          value={this.state.text}
+          onChange={this.handleTextChange}
+        />
+        <input type="submit" value="Post" />
+      </form>
+    );
+  },
+});
+
+ +

事件

+ +

React 使用驼峰命名约定来向一个组件绑定事件处理函数. 我们把onChange处理函数绑 +定到了两个<input>标签, 现在, 当用户向<input>输入文字的时候, 绑定的处理函数 +就会被触发, 然后组件的 state 被修改, 最后当前 state 的修改会被渲染出来.

+ +

提交表单

+ +

现在, 我们修改表单使之可交互. 当用户提交表单的时候, 我们应该向服务器提交请求, +并清空表单, 然后刷新评论列表. 作为开始, 我们先监听表单的 submit 事件.

+ +
// tutorial17.js
+var CommentForm = React.createClass({
+  getInitialState: function () {
+    return { author: "", text: "" };
+  },
+  handleAuthorChange: function (e) {
+    this.setState({ author: e.target.value });
+  },
+  handleTextChange: function (e) {
+    this.setState({ text: e.target.value });
+  },
+  handleSubmit: function (e) {
+    e.preventDefault();
+    var author = this.state.author.trim();
+    var text = this.state.text.trim();
+    if (!text || !author) {
+      return;
+    }
+    // TODO: send request to the server
+    this.setState({ author: "", text: "" });
+  },
+  render: function () {
+    return (
+      <form className="commentForm" onSubmit={this.handleSubmit}>
+        <input
+          type="text"
+          placeholder="Your name"
+          value={this.state.author}
+          onChange={this.handleAuthorChange}
+        />
+        <input
+          type="text"
+          placeholder="Say something..."
+          value={this.state.text}
+          onChange={this.handleTextChange}
+        />
+        <input type="submit" value="Post" />
+      </form>
+    );
+  },
+});
+
+ +

我们把onSubmit处理函数绑定到表单的提交事件上去. 提交后清除表单域. +preventDefault()调用用于阻止浏览器的默认提交动作.

+ +

把回调当做属性

+ +

当用户提交一个评论时, 我们需要刷新评论表以包含新的评论. 因为 CommentBox 有 state +变量, 这就让这个过程显得很直观了.

+ +

我们需要从子组件向父组件传递数据, 我们为父组件的渲染方法增加一个新的回调函数 +(handleCommentSubmit), 并在子组件里面把它绑定到onCommentSubmit事件以调用它. +当这个事件被触发以后, 回调就会被调用

+ +
// tutorial18.js
+var CommentBox = React.createClass({
+  loadCommentsFromServer: function () {
+    $.ajax({
+      url: this.props.url,
+      dataType: "json",
+      cache: false,
+      success: function (data) {
+        this.setState({ data: data });
+      }.bind(this),
+      error: function (xhr, status, err) {
+        console.error(this.props.url, status, err.toString());
+      }.bind(this),
+    });
+  },
+  handleCommentSubmit: function (comment) {
+    // TODO: submit to the server and refresh the list
+  },
+  getInitialState: function () {
+    return { data: [] };
+  },
+  componentDidMount: function () {
+    this.loadCommentsFromServer();
+    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
+  },
+  render: function () {
+    return (
+      <div className="commentBox">
+        <h1>Comments</h1>
+        <CommentList data={this.state.data} />
+        <CommentForm onCommentSubmit={this.handleCommentSubmit} />
+      </div>
+    );
+  },
+});
+
+ +

当用户提交表单的时候, 我们在 CommentForm 组件里面调用这个回调:

+ +
// tutorial19.js
+var CommentForm = React.createClass({
+  getInitialState: function () {
+    return { author: "", text: "" };
+  },
+  handleAuthorChange: function (e) {
+    this.setState({ author: e.target.value });
+  },
+  handleTextChange: function (e) {
+    this.setState({ text: e.target.value });
+  },
+  handleSubmit: function (e) {
+    e.preventDefault();
+    var author = this.state.author.trim();
+    var text = this.state.text.trim();
+    if (!text || !author) {
+      return;
+    }
+    this.props.onCommentSubmit({ author: author, text: text });
+    this.setState({ author: "", text: "" });
+  },
+  render: function () {
+    return (
+      <form className="commentForm" onSubmit={this.handleSubmit}>
+        <input
+          type="text"
+          placeholder="Your name"
+          value={this.state.author}
+          onChange={this.handleAuthorChange}
+        />
+        <input
+          type="text"
+          placeholder="Say something..."
+          value={this.state.text}
+          onChange={this.handleTextChange}
+        />
+        <input type="submit" value="Post" />
+      </form>
+    );
+  },
+});
+
+ +

现在回调已经就位, 我们下面要做的, 就是把数据提交到服务器, 并更新列表

+ +
// tutorial20.js
+var CommentBox = React.createClass({
+  loadCommentsFromServer: function () {
+    $.ajax({
+      url: this.props.url,
+      dataType: "json",
+      cache: false,
+      success: function (data) {
+        this.setState({ data: data });
+      }.bind(this),
+      error: function (xhr, status, err) {
+        console.error(this.props.url, status, err.toString());
+      }.bind(this),
+    });
+  },
+  handleCommentSubmit: function (comment) {
+    $.ajax({
+      url: this.props.url,
+      dataType: "json",
+      type: "POST",
+      data: comment,
+      success: function (data) {
+        this.setState({ data: data });
+      }.bind(this),
+      error: function (xhr, status, err) {
+        console.error(this.props.url, status, err.toString());
+      }.bind(this),
+    });
+  },
+  getInitialState: function () {
+    return { data: [] };
+  },
+  componentDidMount: function () {
+    this.loadCommentsFromServer();
+    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
+  },
+  render: function () {
+    return (
+      <div className="commentBox">
+        <h1>Comments</h1>
+        <CommentList data={this.state.data} />
+        <CommentForm onCommentSubmit={this.handleCommentSubmit} />
+      </div>
+    );
+  },
+});
+
+ +

优化: 优化更新

+ +

我们的列表现在已经 OK 了, 但是它必须等到数据从服务器返回数据才会更新列表, 所以 +看上去会有点慢. 沃恩可以把这个评论添加到列表好让我们的 app 看上去更快些.

+ +
// tutorial21.js
+var CommentBox = React.createClass({
+  loadCommentsFromServer: function () {
+    $.ajax({
+      url: this.props.url,
+      dataType: "json",
+      cache: false,
+      success: function (data) {
+        this.setState({ data: data });
+      }.bind(this),
+      error: function (xhr, status, err) {
+        console.error(this.props.url, status, err.toString());
+      }.bind(this),
+    });
+  },
+  handleCommentSubmit: function (comment) {
+    var comments = this.state.data;
+    // Optimistically set an id on the new comment. It will be replaced by an
+    // id generated by the server. In a production application you would likely
+    // not use Date.now() for this and would have a more robust system in place.
+    comment.id = Date.now();
+    var newComments = comments.concat([comment]);
+    this.setState({ data: newComments });
+    $.ajax({
+      url: this.props.url,
+      dataType: "json",
+      type: "POST",
+      data: comment,
+      success: function (data) {
+        this.setState({ data: data });
+      }.bind(this),
+      error: function (xhr, status, err) {
+        this.setState({ data: comments });
+        console.error(this.props.url, status, err.toString());
+      }.bind(this),
+    });
+  },
+  getInitialState: function () {
+    return { data: [] };
+  },
+  componentDidMount: function () {
+    this.loadCommentsFromServer();
+    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
+  },
+  render: function () {
+    return (
+      <div className="commentBox">
+        <h1>Comments</h1>
+        <CommentList data={this.state.data} />
+        <CommentForm onCommentSubmit={this.handleCommentSubmit} />
+      </div>
+    );
+  },
+});
+
+ +

恭喜你!

+ +

经过上面一系列步骤, 你已经成功的创建了一个可用的评论框. 下面请继续学习更多关于 +react 的使用方法, 你也可以通过阅读 API 手册来 hack. 祝你好运

+ +
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2016/04/14/makefile.html b/2016/04/14/makefile.html new file mode 100644 index 0000000..9395ae3 --- /dev/null +++ b/2016/04/14/makefile.html @@ -0,0 +1,1196 @@ + + + +Makefile 相关 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Makefile 相关

  + +
+
+ + +

Makefile 里面的变量

+ +
    +
  • $< 第一个依赖
  • +
  • $^ 全部依赖
  • +
  • $@ 目标文件
  • +
  • $* ???
  • +
+ + +
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2016/08/16/raining-day.html b/2016/08/16/raining-day.html new file mode 100644 index 0000000..0d91f26 --- /dev/null +++ b/2016/08/16/raining-day.html @@ -0,0 +1,1204 @@ + + + +雨天 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

雨天

  + +
+
+ + +

一月份的上海雨天
+离职,天灰蒙蒙,心情也阴沉
+忘不了朋友送我的一根烟

+ + + +

八月份的北京雨天
+上班,天也是灰蒙蒙,心里五味杂陈
+还记得和你淋过雨的笑声

+ +

城市还是一样的繁华
+雨只是自顾自的下
+我依旧走不出雨天的乌云

+ +

可能我已经用光了所有的好运气
+有些事啊,真是复杂
+我想甩开
+可是它还会到梦里来

+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2017/01/09/learning-mongodb.html b/2017/01/09/learning-mongodb.html new file mode 100644 index 0000000..6aeb7d0 --- /dev/null +++ b/2017/01/09/learning-mongodb.html @@ -0,0 +1,1296 @@ + + + +MongoDB 学习笔记 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

MongoDB 学习笔记

  + +
+
+ + +
+ +
> show dbs
+local  0.000GB
+test   0.000GB
+
+ +

创建数据库

+ +

直接使用某一个数据库就可以创建它

+ +
> use ntest
+switched to db ntest
+> show dbs
+local  0.000GB
+test   0.000GB
+
+ +

不显示新创建的数据库是因为这个数据库是空的, 但它已经确实存在了. +我们插入点数居再看:

+ +
> show collections
+> db.a.insert({'id': 1, 'name': 'song'})
+WriteResult({ "nInserted" : 1 })
+> show collections
+a
+> show dbs
+local  0.000GB
+ntest  0.000GB
+test   0.000GB
+
+ +

插入文档

+ +
> db.a.insert({'id': 2, 'name': 'zhigang'})
+WriteResult({ "nInserted" : 1 })
+> db.a.find()
+{ "_id" : ObjectId("58735159d26d5a97a2a7b78f"), "id" : 2, "name" : "zhigang" }
+> db.a.insert({'id': 1, 'name': 'song'})
+WriteResult({ "nInserted" : 1 })
+> db.a.find()
+{ "_id" : ObjectId("58735159d26d5a97a2a7b78f"), "id" : 2, "name" : "zhigang" }
+{ "_id" : ObjectId("58735171d26d5a97a2a7b790"), "id" : 1, "name" : "song" }
+
+ +

也可以将数据定义为一个变量, 然后执行插入操作, 这样可以复用一部分数据:

+ +
> doc = {'id': 3, 'first-name': 'zhigang', 'last-name': 'song'}
+{ "id" : 3, "first-name" : "zhigang", "last-name" : "song" }
+> db.a.insert(doc)
+WriteResult({ "nInserted" : 1 })
+> db.a.find()
+{ "_id" : ObjectId("58735159d26d5a97a2a7b78f"), "id" : 2, "name" : "zhigang" }
+{ "_id" : ObjectId("58735171d26d5a97a2a7b790"), "id" : 1, "name" : "song" }
+{ "_id" : ObjectId("587351f1d26d5a97a2a7b791"), "id" : 3, "first-name" : "zhigang", "last-name" : "song" }
+> doc['first-name'] = 'xiaoqiang'
+xiaoqiang
+> doc['full-name'] = 'xiaoqiang song'
+xiaoqiang song
+> db.a.insert(doc)
+WriteResult({ "nInserted" : 1 })
+> db.a.find()
+{ "_id" : ObjectId("58735159d26d5a97a2a7b78f"), "id" : 2, "name" : "zhigang" }
+{ "_id" : ObjectId("58735171d26d5a97a2a7b790"), "id" : 1, "name" : "song" }
+{ "_id" : ObjectId("587351f1d26d5a97a2a7b791"), "id" : 3, "first-name" : "zhigang", "last-name" : "song" }
+{ "_id" : ObjectId("58735222d26d5a97a2a7b792"), "id" : 3, "first-name" : "xiaoqiang", "last-name" : "song", "full-name" : "xiaoqiang song" }
+
+ +

插入文档你也可以使用 db.col.save(document) 命令. 如果不指定 _id 字段 save() +方法类似于 insert() 方法, 如果指定 _id 字段, 则会更新该 _id 的数据.

+ +
> db.c.find()
+{ "_id" : ObjectId("587356f1d26d5a97a2a7b793"), "id" : 3, "first-name" : "xiaoqiang", "last-name" : "song", "full-name" : "xiaoqiang song" }
+> db.c.save(doc)
+WriteResult({ "nInserted" : 1 })
+> db.c.find()
+{ "_id" : ObjectId("587356f1d26d5a97a2a7b793"), "id" : 3, "first-name" : "xiaoqiang", "last-name" : "song", "full-name" : "xiaoqiang song" }
+{ "_id" : ObjectId("5873571ed26d5a97a2a7b794"), "id" : 3, "first-name" : "xiaoqiang", "last-name" : "song", "full-name" : "xiaoqiang song" }
+> doc._id = new ObjectId('5873571ed26d5a97a2a7b794')
+ObjectId("5873571ed26d5a97a2a7b794")
+> db.c.save(doc)
+WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
+> db.c.find()
+{ "_id" : ObjectId("587356f1d26d5a97a2a7b793"), "id" : 3, "first-name" : "xiaoqiang", "last-name" : "song", "full-name" : "xiaoqiang song" }
+{ "_id" : ObjectId("5873571ed26d5a97a2a7b794"), "id" : 3, "first-name" : "xiaoqiang", "last-name" : "song", "full-name" : "Zhigang Song" }
+
+ +

可以看到, _id 符合的那条记录被更新了.

+ +

删除集合

+ +
> db.b.insert({'id': 1, 'name': 'song'})
+WriteResult({ "nInserted" : 1 })
+> show collections
+a
+b
+> db.b.drop()
+true
+> show collections
+a
+
+ +

删除数据库

+ +
> db.dropDatabase()
+{ "dropped" : "ntest", "ok" : 1 }
+> show dbs
+local  0.000GB
+test   0.000GB
+
+
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2017/02/07/Redux.html b/2017/02/07/Redux.html new file mode 100644 index 0000000..1a44620 --- /dev/null +++ b/2017/02/07/Redux.html @@ -0,0 +1,1434 @@ + + + +Redux学习笔记 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Redux学习笔记

  + +
+
+ + +

基本概念

+ +

Action

+ +

action 是 Redux store 的唯一获取信息的地方. 它用于从应用向 store 传递数据.

+ + + +

action 就是非常简单的 JavaScript 对象, 一般来说, 它包含了一个 type 字段用于标识自己是那种 action, +然后附带的有些其他字段, 作为 action 的负载供后续使用.

+ +
{
+  type: 'TOGGLE_TODO',
+  id: 1,
+}
+
+ +

Action Creators

+ +

Action Creators 表示创建 action 的函数, 这个函数可以接受 action 里面将要传递的负载, +然后在这个函数里面组装好并返回.

+ +
const createToggleTodoAction = (id) => ({
+  type: "TOGGLE_TODO",
+  id,
+});
+
+ +

Action Creators 可以是异步或者带副作用的. 这些用法在 Redux 的高级教程里可以找到.

+ +

Reducers

+ +

action 只是表明了发生了一个动作, 但是它不知道如何处理这个动作. Reducers 用于处理这些逻辑.

+ +

一个 reducer 就是一个 pure 函数, 它接受前一个 stateaction, 通过 actiontype 字段 +判断是不是自己要处理的数据, 不是的话, 直接返回当前的 state, 是的话, 就执行相应的功能, 产生一个新的 state +并返回.

+ +
const initialState = {
+  todos: [],
+  visiblityFilter: 'SHOW_ALL',
+};
+const todoApp = (state = initialState, action) => {
+  switch (action.type) {
+  case 'TOGGLE_TODO':
+    return Object.assign({}, state, {
+      todos: state.todos.map((item) => {
+        if (item.id == action.id) {
+          return Object.assign({}, item, {
+            completed: !item.completed,
+          });
+        }
+
+        return item;
+      });
+    });
+
+  case 'ADD_TODO':
+    return Object.assign({}, state, {
+      todos: [
+        ...state.todos,
+        {
+          id: action.id,
+          text: action.text,
+          completed: false,
+        }
+      ];
+    });
+
+  case 'SET_VISIBILITY_ATTR':
+    return Object.assing({}, state, {
+      visiblityFilter: action.filter,
+    });
+
+  default:
+    return state;
+  }
+};
+
+ +

一个 reducer 一般只负责改变 state 树的一小部分, 把多个这样的 reducer 组合起来完成所有的工作. +这种方法叫做 reducer composition, 是 Redux 应用中常见的开发模式.

+ +
const visiblityFilter = (state = "SHOW_ALL", action) => {
+  switch (action.type) {
+    case "SET_VISIBILTY_FILTER":
+      return { visiblityFilter: action.filter };
+
+    default:
+      return state;
+  }
+};
+
+const todos = (state = [], action) => {
+  switch (action.type) {
+    case "TOGGLE_TODO":
+      return state.map((item) => {
+        if (item.id == action.id) {
+          return Object.assign({}, item, {
+            completed: !item.completed,
+          });
+        }
+
+        return item;
+      });
+
+    case "ADD_TODO":
+      return [
+        ...state,
+        {
+          id: action.id,
+          text: action.text,
+          completed: false,
+        },
+      ];
+
+    default:
+      return state;
+  }
+};
+
+ +

Combine reducers:

+ +
const todoApp = (state = {}, action) => ({
+  todos: todos(state.todos, action),
+  visiblityFilter: visiblityFilter(state.visiblityFilter, action),
+});
+
+ +

Or:

+ +
import { combineReducers } from "redux";
+const todoApp = combineReducers({
+  todos,
+  visiblityFilter,
+});
+
+ +

combineReducers 生成一个函数, 这个函数向这些细碎的 reducer 传递 state 树中相应的部分, 并获取结果, 最后再把这些结果拼装成完整的 state 树.

+ +

Store

+ +

Redux 里面, store 负责

+ +
    +
  • 存储 state
  • +
  • 通过 store.getState() 获取 state
  • +
  • 通过 dispatch(action) 更新 state
  • +
  • 通过 subscribe(listener) 监听 state 树的改变
  • +
  • 通过 subscribe(lisenter) 返回的 句柄(一个函数) 取消 listener 的监听
  • +
+ +

在 Redux 里面, 只能有一个 store, 当需要拆分逻辑时, 应该使用 reducer composition 而不是多个 store

+ +

写好了 reducer 之后创建 store 非常简单, 把你的 reducer 传递给 createStore 函数就可以了. 如果需要指定 state 的默认值, +把默认值作为第二个参数传递给 createStore 即可:

+ +
import { createStore } from "redux";
+import * as reducers from "./reducers";
+
+const initialState = {
+  todos: [],
+  visiblityFilter: "SHOW_ALL",
+};
+
+const store = createStore(reducers, initialState);
+
+ +

dispatch

+ +

这是一个 action 发送函数, 调用它的时候, 它会给所有的 reducer 发送 action 消息.

+ +
store.dispatch(createToggleTodoAction(1));
+
+ +

可以直接调用 store.dispatch, 可以使用 bindActionCreators 函数来绑定 Action Creator 和 dispatch

+ +

使用 React 的话, 官方推荐的做法是使用 react-redux 库提供的 connect 函数. connect 函数可以帮助把 +state 以及 Action Creators 映射到组件的 props 里.

+ +

subscribe

+ +

订阅函数, 通过它, 可以及时的知道 store 里面 state 的改变, 并作出相应的处理.

+ +

数据流动

+ +

Redux 是严格的单项数据流(unidirectional data flow). 数据的生命周期如下:

+ +
    +
  1. 调用 store.dispatch(action) 发送(广播)动作.
  2. +
  3. store 现在会调用向 store.subscribe 注册的函数(reducers), +这些函数接受当前的状态树以及动作作为参数, 计算并返回下一个状态树.
  4. +
  5. 根 reducer 会把各个小的 reducer 产生的状态树部分组合起来, 形成完整的状态树.
  6. +
  7. store 保存根 reducer 返回的状态树.
  8. +
+ +

以上四步走完之后, app 就可以通过获取下一个状态树来更新了.

+ +

Use with React

+ +

个人认为, React 和 Redux 结合的关键在设计所谓的状态树结构以及容器组件.

+ +

状态树结构设计好了, 才能清晰的拆分出来 reducers, 而容器组件可以向它里面包含的子组件注入 +Redux 的 dispatcher 等数据.

+ +

状态树就是一个 JavaScript 对象, 容器组件简单点說就是一个 React 组件, 但是这个组件通过 +store.subscribe 函数订阅了 Redux 的状态树, 然后把这些状态树以 props +的形式传递到子组件里面去, 可以选择自己写容器组件, 但是 ‘react-redux’ 包提供了一个 +connect 函数, 不光可以把组件转化为容器组件, 还为我们做了些必要的优化, 这样你就不用自己 +考虑性能问题, 到处使用 shouldComponentUpdate 了, 所以推荐使用这个函数.

+ +

要使用 connect 函数, 我们需要定义一个叫做 mapStateToProps 的特殊的函数, 这个函数用于把 +当前的 Redux 状态树把数据作为 props 映射到你的显示组件上. 在函数里面, 可以基于当前状态 +树的数据, 进行数据整合, 过滤等操作使之适应 UI 展示. 函数最后返回的对象会作为 +props 映射到容器组件上.

+ +
const getVisibleTodos = (todos, filter) => {
+  switch (filter) {
+    case "SHOW_ALL":
+      return todos;
+    case "SHOW_COMPLETED":
+      return todos.filter((t) => t.completed);
+    case "SHOW_ACTIVE":
+      return todos.filter((t) => !t.completed);
+  }
+};
+
+const mapStateToProps = (state) => {
+  return {
+    todos: getVisibleTodos(state.todos, state.visibilityFilter),
+  };
+};
+
+ +

除了读取状态树之外, 容器组件也可以调度 action, 类似 props +这里可以定义一个叫做 mapDispatchToProps 的函数, 它接受 store 里面的 +dispatch 函数作为参数, 返回回调函数对象, 这些回调函数会作为 props +映射到容器组件上去.

+ +
const mapDispatchToProps = (dispatch) => {
+  return {
+    onTodoClick: (id) => {
+      dispatch(toggleTodo(id));
+    },
+  };
+};
+
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2017/02/11/Redux-advance.html b/2017/02/11/Redux-advance.html new file mode 100644 index 0000000..695df80 --- /dev/null +++ b/2017/02/11/Redux-advance.html @@ -0,0 +1,1362 @@ + + + +Redux高级用法 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Redux高级用法

  + +
+
+ + +

Logger

+ +

开发过程中, 如果能自动打印每次发送的 action 以及 reducers +处理前后的状态树, 那么对程序的调试是很有帮助的.

+ +

社区提供了一个这样的库, 叫做 redux-logger, 它的使用方法如下:

+ + + +
import createLogger from "redux-logger";
+import { createStore, applyMiddleware } from "react-redux";
+
+const logger = createLogger();
+const store = createStore(reducer, initialState, applyMiddleware(logger));
+
+ +

关于 applyMiddleware 将会在下面介绍. 经过上面的代码处理之后, 每次接收到 +action 都会自动打印处理前的状态树, action 对象, 处理后的状态树.

+ +

异步

+ +

我们一再强调, reducer 必须是纯函数. 那么, 当希望请求 API +数据的时候应该怎么做呢?

+ +

Redux 允许 actionCreator 不是纯函数, 我们可以在这里面请求 API, 并作出相应 +的动作. 这种情况下, 我们的 actionCreator 返回的可能就不是单纯的 JavaScript +对象了, 也可能是一个函数. 但是 Redux 默认是不允许返回函数的, 这时候, 可以引入 +redux-thunk 库来解决这个问题.

+ +

redux-thunk 是 Redux 的一个中间件, 它允许 actionCreator 返回一个接受 dispatch +的函数, 并立即执行这个函数, 有了它, 就可以在返回函数里面做一些异步操作了, +当然, 也可以做更多的 action dispatch.

+ +
import { createStore, applyMiddleware } from "redux";
+
+import thunkMiddleware from "redux-thunk";
+
+const store = createStore(
+  reducer,
+  initialState,
+  applyMiddleware(thunkMiddleware)
+);
+
+ +

异步的 action 可以像下面一样定义:

+ +
const fetchedPost = (post) => ({
+  ...post,
+  type: "FETCHED_POST",
+});
+
+const getPostAction = (post_id) => (dispatch) => {
+  return fetch(`http://some.api.com?post_id=${post_id}`)
+    .then((response) => response.json())
+    .then((post) => dispatch(fetchedPost(post)));
+};
+
+ +

Middleware

+ +

下面来了解一下中间件的实现.

+ +

如何由 0 到写出完整的中间件, 在 Redux 官方文档里面有简单的介绍. +这里分析一下官方对中间件的实现.

+ +

首先来看 Redux 是如何应用中间件的.

+ +
import { createStore, applyMiddleware } from "redux";
+import thunk from "redux-thunk";
+import createLogger from "redux-logger";
+
+const logger = createLogger();
+const store = createStore(
+  reducer,
+  initialState,
+  applyMiddleware(thunk, logger)
+);
+
+ +

中间件是一类类似下面这样的函数:

+ +
const logger = (store) => (next) => (action) => {
+  console.group(action.type);
+  console.info("dispatching", action);
+  let result = next(action);
+  console.log("next state", store.getState());
+  console.groupEnd(action.type);
+  return result;
+};
+
+ +

接下来是 applyMiddleware 的官方实现, 略微有些复杂, 先看代码, 后面会有注解.

+ +
function applyMiddleware(...middlewares) {
+  return (createStore) => (reducer, preloadedState, enhancer) => {
+    var store = createStore(reducer, preloadedState, enhancer);
+    var dispatch = store.dispatch;
+    var chain = [];
+
+    var middlewareAPI = {
+      getState: store.getState,
+      dispatch: (action) => dispatch(action),
+    };
+    chain = middlewares.map((middleware) => middleware(middlewareAPI));
+    dispatch = compose(...chain)(store.dispatch);
+
+    return {
+      ...store,
+      dispatch,
+    };
+  };
+}
+
+ +

首先, applyMiddleware 接受一系列的 middleware, 这个调用返回了一个闭包函数, +然后这个闭包函数又返回了一个闭包函数, 这两次的操作目地是一样的, 就是能让最后返回 +的这个闭包函数访问到前两个函数传进来的参数, 最后这个闭包函数的形式实际上和 +createStore 函数一样.

+ +

假设我们在调用 applyMiddleware 函数的时候传进来了一些中间件, 然后调用返回的 +闭包函数时, 传入的是 Redux 的 createStore 函数, 最后一次闭包函数调用, +传入的是与上面示例代码里面传给 createStore 一样的参数(实际上会把中间件那部分参数去掉).

+ +

那么, 在最里层的函数里面, 我们可以访问中间件数组, createStore 函数, 以及 +reducersinitialState. 在这个函数里, 我们调用 createStore 来创建一个 +store, 之后创建一个中间件调用半成品数组, 这个半成品是接受 next dispatch +函数的闭包函数, 然后把这个数组交给后面的 compose 函数进一步处理.

+ +
function compose(...funcs) {
+  if (funcs.length === 0) {
+    return (arg) => arg;
+  }
+
+  if (funcs.length === 1) {
+    return funcs[0];
+  }
+
+  const last = funcs[funcs.length - 1];
+  const rest = funcs.slice(0, -1);
+  return (...args) =>
+    rest.reduceRight((composed, f) => f(composed), last(...args));
+}
+
+ +

compose 函数接受一系列的函数(上面的半成品数组, 记做funcs), 然后返回一个闭包函数. +这个闭包函数接受的也是一系列的函数(实际上就一个, store.dispatch), 然后它做了一个 +reduceRight 操作, 这个操作的结果就是, funcs 数组里面的函数, 从右向左, 一次调用, +最后那次调用的参数是这个闭包函数接受的那个 args 数组. 由于每个 middleware 半成品函数 +接受的是上一个中间件修改过的 dispatch 函数(或者是第一个中间件接受原生的 dispatch), +返回的是自己修改过的具有增强功能的 dispatch 函数. 这么一处理, 就很巧秒的实现了中间件 +调用中间件在调用中间件….这样的调用链.

+ +

搞明白 applyMiddleware 的调用过程之后, 再来看看 createStore 内部是是如何调用 +applyMiddleware 的.

+ +
function createStore(reducer, preloadedState, enhancer) {
+  if (typeof preloadedState === "function" && typeof enhancer === "undefined") {
+    enhancer = preloadedState;
+    preloadedState = undefined;
+  }
+
+  if (typeof enhancer !== "undefined") {
+    if (typeof enhancer !== "function") {
+      throw new Error("Expected the enhancer to be a function.");
+    }
+
+    return enhancer(createStore)(reducer, preloadedState);
+  }
+
+  /* more code... */
+}
+
+ +

这里接受的 enhancer 实际上是 applyMiddleware 调用后返回的闭包函数了, 把 createStore +函数传递给它, 得到 applyMiddleware 里面的最内层闭包函数, 然后把 reducerpreloadedState +作为参数传到最内层函数, 之后在 applyMiddleware 最内层的那个闭包函数里面, 创建出来增强过的 store.

+
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2017/02/26/lisp-basic.html b/2017/02/26/lisp-basic.html new file mode 100644 index 0000000..660119a --- /dev/null +++ b/2017/02/26/lisp-basic.html @@ -0,0 +1,1453 @@ + + + +Lisp 入门 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Lisp 入门

  + +
+
+ + +

心血来潮, 想学下 Lisp 或者 Haskell, 正好发现了Build Your Own Lisp +这本书, 所以先从 Lisp 入手.

+ + + +

基本概念

+ +

表达式要用括号 () 括起来, 括号中第一项内容为操作符, 后面的是它的操作域(操作数). +这种便是方法称为前缀表示法, 也叫波兰表示法.

+ +
(+ 1 2)
+(sqrt 16)
+
+ +

数学上, 函数用 f(x) 表示, 在 lisp 中, 也要使用前缀表示法表示:

+ +
(f x) ;f(x)
+(g x y) ;g(x, y)
+(h (g x)) ;h(g(x))
+(< 3 4) ;比较大小, 3 < 4
+(and T T) ;true and true
+(or NIL T) ;false or true
+(or (< 3 4) (> 3 4)) ;3 < 4 or 3 > 4
+
+ +

+ +

Lisp 即所谓的 List procesor, 表处理语言. 上文中出现的括号, 都是表. +原子是不包含空格的字符, 不如上面的 +, sqrt, 操作数等等, 表里面 +不见得都是原子元素, 表是可以嵌套的, 比如上面最后一个 or 操作, +就属于嵌套表.

+ +

表的大小没有限制, 最简单的 () 是一个空表, 空表是 Lisp 里面既是表又 +是原子的东西. 当原子讲的时候, 表示逻辑假(NIL). 上文中, 我们还遇到了 +T, 这个表示逻辑真.

+ +

程序和数据

+ +

编程就是写一些程序, 处理数据. 在 Lisp 中, 程序和数据都用表来表示. +但是表示程序的表, 第一项一定是一个操作符, 否则就会报错:

+ +
(+ 1 2) ;正常的 lisp 程序
+(1 2 3) ;这个第一项不是操作符, 会报错
+
+ +

表示数据的表现不需要以操作符开头, 但是, 我们需要告诉 lisp 解释器, +不要试图计算这些数据表项的值, 这里用到了 quote 操作符.

+ +
(quote (1 2 3))
+'(1 2 3)
+
+ +

上面两种写法是等价的, 下面写起来更简单, 键盘会坏的慢一些.

+ +

Lisp 里面不区分大小写, 如果我们在 REPL 里面输入 '(Hello World), +我们会得到 (HELLO WORLD) 作为输出.

+ +

基本操作

+ +

CAR 取表的第一项

+ +

CAR 操作符用来提取表的第一项.

+ +
(car '(1 2 3)) ;输出1
+(car '((1 2) 3)) ;输出(1 2)
+
+ +

CDR 删除表的第一项

+ +

CDR 用于删除表的第一项.

+ +
(cdr '(1 2 3)) ;输出(2 3)
+(cdr '((1 2) 3)) ;输出(3)
+
+ +

CXR 组合操作

+ +

所谓 CXR 组合操作, 就是 CARCDR 的结合使用, 灵活使用, +可以取表的第二项, 第三项…

+ +
(car (cdr '(1 2 3))) ;输出2
+(cadr '(1 2 3)) ; 同上, 输出2
+
+(cdr (car '((1 2) 3))) ;输出(2)
+(cdar '((1 2) 3)) ; 同上, 输出(2)
+
+ +

类似的, 还可以组合出来CDAAR, CADDR … 一系列的操作出来. 作为初学者, +这么多括号我已经晕菜了, 难怪会有彩虹括号这种东西出现…

+ +

CONS

+ +

上面的操作是用来拆分一个表的, 现在试着用 cons 来合并表.

+ +
(cons (1) 2) ;输出(1 . 2)
+(cons 1 (2)) ;输出(1 2)
+
+ +

我们說括号扩起来的语句叫做表, 这种执行运算的也叫做 S-表达式, +表表示的实际上是一个二叉树, 在 S-表达式 里面, 这种二叉树记为 (Left . Right) +如果左支是一个表, 那么, 就会记做 ((Left) . Right), 如果右支是一个表, +那么记做 (Left . (Right)), 这种右支还可以把点号省略记做(Left Right)

+ +

上面的 CDR 操作会删掉表的第一项, 实际上它做的工作是取出表除了第一项之外后面 +的项, 按照上面的解释, 这个操作取的实际上是这个二叉树的右子树.

+ +

APPEND 合并两个表

+ +

cons 是在左右子树上进行的合并操作, append 将会在右子树上添加新的节点. 比如

+ +
(append '(1 2) '(3 4)) ;输出(1 2 3 4) <=> (1 . (2 3 4))
+(cons '(1 2) '(3 4)) ;输出((1 2) 3 4) <=> ((1 2) . (3 4))
+
+ +

LIST 函数

+ +

list 函数会将所有传递给它的参数都放到一个表里面, 然后返回它.

+ +
(list '(1) 2 3 '(4 5)) ;输出 ((1) 2 3 (4 5))
+
+ +

再看原子

+ +

原子可以是任何数, 分数, 小数, 自然数, 负数等等. 原子可以是一个字母排列, 当然其中 +可以夹杂数字和符号. 空表就是原子 NIL.

+ +

判断一个元素或者一个字符是不是原子, 可以使用 atom 运算符.

+ +
(atom 'a) ;输出 T
+(atom '(a b)) 输出: NIL
+
+ +

变量赋值可以使用 setq 运算符. setq 运算符的作用就是将紧接着的变量赋值, +这个赋值语句的返回值是这个变量被赋予的值.

+ +
(setq a 5)
+(+ a 7) ;输出 12
+(cons a '(1)) ;输出 (5 1)
+
+ +

断言函数

+ +

断言函数包括前面遇到的 atom 函数, 以及这里将要介绍的 nullequal 函数.

+ +

null 函数判断一个表达式是否为 NIL, 是返回 T, 否则返回 NIL.

+ +

equql 函数判断两个值是否完全相等.

+ +
(null ()) ;输出T
+(null 'a) ;输出NIL
+(null (- 1 1)) ;输出NIL
+(equal 'a 'b) ;输出NIL
+(equal 'a 'a) ;输出T
+(equal (+ 1 2) 3) ;输出 T
+
+ +

定义自己的函数

+ +

defun 操作符用来定义自己的函数, 其形式为:

+ +
(defun function_mame (arg1 arg2 ...)
+ (function_body)
+)
+
+ +

函数的返回值是 function_body 运算完成之后的那个值. +比如:

+ +
(defun addition (x y)
+ (+ x y)
+)
+
+(addition 1 2)
+
+ +

在 REPL 输入以上代码, 将会得到加法运算结果: 3

+ +

下面介绍两个系统自带函数: first, last.

+ +

first 就是我们前面预见的 car 函数. cdr 对应的叫做 rest, 而这里的 +last 取的是右子树(或者左子树, 如果没有右子树的话)最后一个节点.

+ +

接下来我们定义一个函数, 取列表的首尾两个元素.

+ +
(defun ends (l)
+ (cons (first l) (last l))
+)
+
+ +

条件操作符

+ +

cond 表示条件操作符.

+ +

cond 操作符有些复杂, 它的形式为

+ +
(cond 分支列表1 分支列表2 分支列表3 ... 分支列表N)
+
+ +

而其中分支列表的构成为 (条件 p 值 e)

+ +

cond 操作符将对每一个”条件 p”求值, 如果为 NIL. 就接着求下一个, 如果为真, +就返回相应的”值 e”, 如果没有一个真值, cond 操作符返回 nil. cond 操作 +符的参数可以不止两个

+ +
(cond (nil 1) (nil 2) (t 3)) ;输出3
+(cond (a 1) (nil 2) (t 3)) ;输出1
+
+ +

利用条件操作符实现的最大值求值

+ +
(defun max_number (a b)
+ (cond ((> a b) a) (1 b))
+)
+
+ +

上面这段代码也可以写成下面这样, 下面这段使用了 if 函数.

+ +
(defun max_number (a b)
+ (if (> a b) a b)
+)
+
+ +

递归函数

+ +

比如要求等差数列 a(n) = a(n-1) + 2 的第 N 项. 那么, 递归函数可以定义为:

+ +
(defun desq (n)
+ (if (= n 0) 0 (+ (desq (- n 1)) 2))
+)
+
+ +

递归函数这里 lisp 提供了一个 trace 函数来帮助查看函数调用栈.

+ +
lisp> (trace desq)
+lisp> (desq 4)
+
+# 我们得到了一下输出
+1. Trace: (DESQ '4)
+2. Trace: (DESQ '3)
+3. Trace: (DESQ '2)
+4. Trace: (DESQ '1)
+5. Trace: (DESQ '0)
+5. Trace: DESQ ==> 0
+4. Trace: DESQ ==> 2
+3. Trace: DESQ ==> 4
+2. Trace: DESQ ==> 6
+1. Trace: DESQ ==> 8
+8
+
+ +

lisp 里面的 7 大公理(七个操作符)

+ +

Lisp 有 7 个基本操作符(实际上或许可以再精简). 这 7 个基本操作符就像几何中的 +公理一样, 任何其他函数都可以由这七大公理定义. 也就是说, 7 个基本操作符包 +含了 Lisp 的所有语义.

+ +

这 7 个基本操作符是:

+ +
    +
  1. Quote
  2. +
  3. Atom
  4. +
  5. Eq
  6. +
  7. Car
  8. +
  9. Cdr
  10. +
  11. Cons
  12. +
  13. Cond
  14. +
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2017/03/10/json-c.html b/2017/03/10/json-c.html new file mode 100644 index 0000000..f404344 --- /dev/null +++ b/2017/03/10/json-c.html @@ -0,0 +1,1233 @@ + + + +json c 笔记 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

json c 笔记

  + +
+
+ + +

JSON-C 使用 json_object 结构体来表示 json +对象, 可以使用 json_object_to_json_string_ext 函数来输出一个 json 对象. +json_object_to_json_string 函数是前一个函数的包装.

+ + + +
jsob_object *json_obj = json_object_new_object();
+
+ +

Json 对象有几种数据类型, 对应的 json-c 库有这几个基本类型的创建方法.

+ +
struct json_object * json_object_new_boolean(json_bool b);
+json_bool json_object_get_boolean(struct json_object *obj);
+
+struct json_object * json_object_new_int(int32_t i);
+int32_t json_object_get_int(struct json_object *obj);
+
+struct json_object * json_object_new_int64(int64_t i);
+int64_t json_object_get_int64(struct json_object *obj);
+
+struct json_object * json_object_new_double(double d);
+struct json_object * json_object_new_double_s(double d, const char *ds);
+double json_object_get_double(struct json_object *obj);
+
+struct json_object * json_object_new_string(const char *s);
+struct json_object * json_object_new_string_len(const char *s, int len);
+const char * json_object_get_string(struct json_object *obj);
+int json_object_get_string_len(struct json_object *obj);
+
+ +

json_object_new_double_s() 允许更精确的双精度浮点表示.

+ +

json_object_new_string_len() 更据字符串的长度来来创建一个 json 字符串对象.

+ +

复合类型可以相互嵌套.

+ +
struct json_object *json_object_new_array(void);
+struct array_list *json_object_get_array(struct json_object *obj);
+int json_object_array_length(struct json_object *obj);
+void json_object_array_sort(struct json_object *jso, int(*sort_fn)(const void *, const void *));
+int json_object_array_add(struct json_object *obj, struct json_object *val);
+int json_object_array_put_idx(struct json_object *obj, int idx, struct json_object *val);
+struct json_object *json_object_array_get_idx(struct json_object *obj, int idx);
+
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2017/05/18/python-official-extension-tutorial.html b/2017/05/18/python-official-extension-tutorial.html new file mode 100644 index 0000000..50c8c84 --- /dev/null +++ b/2017/05/18/python-official-extension-tutorial.html @@ -0,0 +1,1645 @@ + + + +Python 官方文档扩展部分笔记 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Python 官方文档扩展部分笔记

  + +
+
+ + +

概述

+ +

文件名

+ +

如果模块的名字是 spam, 那么我们的模块文件名字一般回叫做 spammodule.c, 但是对于一些比较长的模块名字, 也可以直接使用模块名来命名, 比如像 spammify.

+ + + +

头文件

+ +
#include <Python.h>
+
+ +

Python.h 文件包含了为 python 写扩展必要的一些数据结构和定义.

+ +

Python.h 里面除了标准库符号之外, 定义的所有用户可见的符号, 都会以 Py 或者 PY 开头. 由于在 Python 解释器里面广泛的使用也为了方便起见, Python.h 也包含了一些标准库文件. Python.h 做了一些可能会影响标准库的定义, 所以应该在其他标准库引入之前就引入.

+ +
+

Python.h 引入的几个标准库文件: , , , and

+
+ +

如果上述的一些文件在系统上不存在, 那么 Python.h 会直接自己声明 malloc(), free() 以及 realloc() 函数.

+ +

创建一个可以调用的函数

+ +

接下来我们写一个可以在 Python 里面调用的函数. Python 代码为: spam.system("ls -al").

+ +
static PyObject *
+spam_system(PyObject *self, PyObject *args)
+{
+  char *command;
+  int status;
+
+  if (!PyArg_ParseTuple(args, "s:system", &command)) {
+    return NULL;
+  }
+
+  status = system(command);
+
+  return Py_BuildValue("i", status);
+}
+
+ +

这个就是 Python 调用的 C 函数. 它总是接受两个参数, 一般叫做 selfargs.

+ +

self 参数对于在模块顶层的函数来说, 它指向模块对象. 对于方法函数来说,, 它指向的是对象实例.

+ +

args 参数, 指向的是一个 Python 元组对象, 每个元组项目, 都会对应一个调用的参数, 参数都是 Python 对象, 所以为了在 C 代码里使用他们, 我们要把他们转化成为 C 值. PyArg_ParseTuple 函数用于检测参数类型, 并且把他们转化为 C 值. 它使用一个模板字符串来决定每个参数的类型, 以及之后接受的用于保存值的 C 参数类型. 关于这个函数, 后面有更详细的描述

+ +

PyArg_ParseTuple 在所有参数无误, 并且都被正确的转化为对应的 C 值的时候, 会返回非 0. 在参数类型有误的情况下会返回 0, 在这种情况中, 它也会触发一个异常, 所以调用函数立即返回 NULL. (在 Python 解释器收到 NULL 的时候, 它就知道有一个异常被抛出了)

+ +

插曲: 错误和异常

+ +

一个很重要的贯穿整个 Python 解释器的约定是: 当一个函数执行失败, 它应当设定一个异常条件并且返回一个错误值(通常是 NULL). 异常保存在一个解释器的静态全局变量里面, 当这个变量是 NULL 标识没有异常发生, 第二个全局变量是保存异常”相关值”的(the second argument to raise, 我没理解是 raise 的那个参数…). 第三个全局变量是, 保存 Python 代码里面的异常调用栈. 这三个变量, 相当于 Python 代码执行 sys.exc_info(). 理解这些变量对理解如何传递错误是很重要的.

+ +

Python 的 API 定义了一系列的函数, 来定义异常类型.

+ +

最常见的就是 PyErr_SetString 函数, 它的参数是异常对象和 C 字符串, 异常对象总是一个预定义好的对象, 比如: PyExc_ZeroDivisionError, C 字符串用来说明发生错误的原因, 并且会被转化为 Python 字符串, 保存在异常”相关值”那个变量里面.

+ +

另一个有用的函数是 PyErr_SetFromErron, 它接受一个异常参数, 然后通过全局的 errno 变量来构造”相关值”. 最通用的函数是 PyErr_SetObject, 他接受两个参数, 一个异常加一个相关值, 对这些函数传入的参数, 不需要对他们施行 Py_INCREF 操作.

+ +

可以使用 PyErr_Occurred() 函数来执行非破坏性的检查操作, 这个函数返回当前的异常对象, 如果没有异常发生, 他就返回 NULL. 因为多数情况下, 你可以从返回值来判断是不是有异常发生, 所以你基本上不怎么需要调用这个函数.

+ +

当一个函数 f 调用了 g, 而 g 发生了异常, f 也应当返回一个错误值(通常情况是 NULL 或者 -1). 它不能调用 PyErr_*() 函数, 因为 g 已经调用过了. 调用 f 的函数, 也应该向它的调用函数返回一个错误标记, 一样也不需要调用 PyErr_*(), 如此一致往前. 错误的详细信息已经在第一次探测到他的地方被详细报告了. 当错误到达 Python 解释器主循环的时候, 就会打断当前的执行代码, 并且尝试着查找由 Python 程序员编写的异常处理.

+ +

有些情况下, 模块可以调用 PyErr_*() 来给出更详细的错误信息, 这种情况是可以的. 但是作为一个通用的规则, 这个调用不是必需的, 并且可能会导致造成错误的信息丢失, 因为很多操作会因为各种原因导致失败

+ +

要忽略一个失败函数调用产生的异常, 我们必须显式的调用 PyErr_Clear 函数来清除异常数据. C 代码里面, 唯一需要调用这个函数的情况是, 当我们不希望向 Python +解释器传递这个异常, 而是打算自己处理这个异常(比方说, 尝试其他东西, 或者直接忽略掉异常)

+ +

每次调用 malloc() 失败, 都应该转化成一个异常, 直接调用 malloc() 或者 realloc() 的函数, 必须调用 PyErr_NoMemory() 并返回失败. 所有的对象创建函数(比如: PyLong_FromLong)都已经做了这种操作, 所以这个提醒是给那些直接调用内存分配函数的地方的.

+ +

注意, PyArg_ParseTuple 函数和类似的函数, 这些函数会犯回一个正数或者 0 来标识执行成功, 返回 -1 来表示执行失败. 就像 Unix 系统调用那样.

+ +

最后, 再犯会错误值的时候, 要小心垃圾清理工作(通过对对象调用Py_XDECREF 或者 Py_DECREF)

+ +

应该抛出那个异常, 完全有你来决定, 对所有的内置异常类型都有对应的预定义 C 对象, 比如像 PyExc_ZeroDivisionError, 这种你可以直接使用. 当然你应该选择明确的异常类型, 不要把 PyExc_TypeError 用来标识文件无法打开, 那明明就是 PyExc_IOError 嘛. 如果你的传参不对, PyArg_ParseTuple 函数一般会抛出 PyExc_TypeError. 如果你的参数值, 应该是莫个范围, 或者要满足某些条件, PyExc_ValueError 会更合适.

+ +

你也可以给你的模块定义一个新的异常, 可以通过在文件开始声明一个静态的对象变量来实现:

+ +
static PyObject *SpamError;
+
+ +

之后在模块初始化函数里面, 用一个异常对象初始化它(现在我们先忽略错误检查).

+ +
PyMODINIT_FUNC
+PyInit_spam(void)
+{
+  PyObject *m;
+
+  m = PyModule_Create(&spammodule);
+  if (m == NULL) {
+    return NULL;
+  }
+
+  SpamError = PyErr_NewException("spam.error", NULL, NULL);
+  Py_INCREF(SpamError);
+  PyModule_AddObject(m, "error", SpamError);
+
+  return m;
+}
+
+ +

请注意, 异常的 Python 名称是 spam.error. PyErr_NewException 函数可以基于基 Exception 类创建新对象(除非另外又传进来一个非 NULL 的类), Python 的内战异常文档里有对基异常类相关的描述.

+ +

注意 SpamError 保留了一个新创键对象的引用, 这是有意而为之的. 应为异常可能会被外部代码删掉, 所以又有一个对这个类的引用可以确保他不会被删掉, 保证了 +SpamError 不会成为悬挂指针(野指针). 如果它成了一个野指针, C 代码就会生成 core dump 错误或者其他未维护的副作用.

+ +

在问找稍后会介绍 PyMODINIT_FUNC 这种函数返回类型.

+ +

之后, SpamError 就可以和 PyErr_SetString 函数一起使用了, 向下面这样:

+ +
static PyObject *
+spam_system(PyObject *self, PyObject *args)
+{
+  char *command;
+  int sts;
+
+  if (!PyArg_ParseTuple(args, "s", &command)) {
+    return NULL;
+  }
+
+  sts = system(command);
+  if (sys < 0) {
+    PyErr_SetString(SpamError, "System command filed");
+    return NULL;
+  }
+
+  return PyLong_FromLong(sts);
+}
+
+ +

继续例子

+ +

现在, 我们回到代码. 你应该能理解下面的函数什么意思了:

+ +
if (!PyArg_ParseTuple(args, "s", &command))
+    return NULL;
+
+ +

如果在参数列表里面找到错误, 那么它就会返回 NULL, 异常是由 PyArg_ParseTuple 函数设置的. 如果没有错误, 参数就会被拷贝到本地的 command 变量里面这是一个指针赋值, 并且你不应该尝试着取改变指针指向的内容. 在 C 里面, 可以声明为 const char *command.

+ +

下一个语句是, 把我们从 PyArg_ParseTuple 函数获取到的参数, 传递进 Unix 系统调用 system 里面去.

+ +
sts = system(command);
+
+ +

我们的 spam.system() 应该返回 Python 对象类型, 所以使用 PyLong_FromLong() 函数来生成一个长整型的 Python 数字.

+ +

如果你的某个 C 函数, 不需要返回值(返回 viod). 那么对应的 Python 函数必须返回 None. 可以使用用下面这段代码做到(有一个 Py_RETRUN_NONE 宏来做这个事).

+ +
Py_INCREF(Py_None);
+return Py_None;
+
+ +

Py_None 是 Python 特殊对象 None 的 C 名称. 这个名称和 NULL 不同, NULL 表示发生了错误.

+ +

模块方法表和初始化函数

+ +

我说过会详细解释 spam_system() 函数是如何在 Python 代码里面调用的. 首先, 我们需要把函数的名字和地址列到方法表里面.

+ +
static PyMethodDef SpamMethods[] = {
+  {"system", spam_syatem, METH_VARGARS, "Execute a shell command."},
+  {NULL, NULL, 0, NULL} /* sentinel */
+};
+
+ +

请注意第三个 METH_VARGARS, 这个标记告诉 Python 解释器调用转换用于 C 函数. 这个值总应该是 METH_VARGARS 或者 METH_VARGARS | METH_KEYWORDS. 传 0 意味着是一个 PyArg_ParseTuple 变体(这段不知道怎么组织语句, 我自己大概理解了, 下面保留原文).

+ +

Note the third entry (METH_VARARGS). This is a flag telling the interpreter the calling convention to be used for the C function. It should normally always be METH_VARARGS or METH_VARARGS | METH_KEYWORDS; a value of 0 means that an obsolete variant of PyArg_ParseTuple() is used.

+ +

当使用 METH_VARGARS 的时候, 函数应该将 “Python 级别” 的参数转换为一个 PyArg_ParseTuple 可以接受的元组, 关于这个函数的更多信息, 我们在下面详细描述.

+ +

如果想要向函数传递关键字参数, 那么应当设置 METH_KEYWORDS 位. 在这种情况下, C 函数接受一个 PyObject * 关键字字典对象, 应该使用 PyArg_ParseTupleAndKeywords() 函数来解析这种参数.

+ +

在模块定义结构中, 必须有对方法表的引用:

+ +
static struct PyModuleDef spammodule = {
+   PyModuleDef_HEAD_INIT,
+   "spam",   /* name of module */
+   spam_doc, /* module documentation, may be NULL */
+   -1,       /* size of per-interpreter state of the module,
+                or -1 if the module keeps state in global variables. */
+   SpamMethods
+};
+
+ +

这个结构体, 又必须传送给解释器的模块创建函数, 模块初始化函数必须命名为 PyInit_name 这样, name 是模块的名字, 并且应该在模块文件里面定义为 non-static 的(C 基础, 定义为 static 的在文件外部是不可见的, 这个是模块入口, 显然不能定义成 static 的).

+ +
PyMODINIT_FUNC
+PyInit_spam(void)
+{
+    return PyModule_Create(&spammodule);
+}
+
+ +

PyMODEINIT_FUNC 声明这个函数返回类型是 PyObject * 的, 它还做了一些关于平台要求的特殊链接的声明, 以及 C++ 的声明函数为 ectern "C".

+ +

当 Python 程序员第一次引入 spam 模块, PyInit_spam 就会被调用(参看下面关于内嵌 python 的论述). 这个函数调用 PyModule_Create, 这个函数返回一个模块对象. 它根据模块里面定义的方法表(PyMethodDef 结构体数组)把内置函数对象插入到新创建的模块上去, 然后返回刚刚创建的这个模块对象的指针. 它可能会因为创建错误而产生致命错误, 或者因为无法圆满的创建模块而返会 NULL. 初始化函数必须向它的调用者返回创建的模块对象, 调用者会把对象插入到 sys.models 里面去.

+ +

当内嵌 Python 的时候, PyInit_spam 函数除非在 PyImport_Inittab 表里面有一个入口, 否则不会自动的被调用. 要把一个模块插入到初始表里面, 使用 PyImport_AppendInittab 函数, 跟上一个可选的模块导入语句.

+ +
+

在同一个线程里面, 从 sys.module 里面移除或者引入编译模块给多个解释器(或者后面跟着使用 fork 函数而没有使用 exec 函数), 可能会导致问题. 模块作者在初始某块结构的时候要小心谨慎.

+
+ +

Python 源码里面的 Modules/xxxmodule.c 是很翔实的某块例子. 这些代码用来阅读学习或者作为模板很不错.

+ +
+

Note Unlike our spam example, xxmodule uses multi-phase initialization (new in Python 3.5), where a PyModuleDef structure is returned from PyInit_spam, and creation of the module is left to the import machinery. For details on multi-phase initialization, see PEP 489.

+
+ +

编译与链接

+ +

开始使用你的模块之前, 有个重要的工作要做: 编译并链接到 Python 系统. 如果你使用动态链接, 那么连接的细节会和你使用的系统的动态链接方式有关系, 更多的可以去查看本文档编译相关的章节

+ +

如果你不使用动态链接, 或者想把你的扩展做成 Python 解释器的一部分, 那么你要修改解释器的配置, 并重新编译它. 所幸的是, 在 Unix 上做这些工作很容易, 只要把你的模块文件放到 Modules 文件夹下, 然后解压缩源代码, 并在 Modules/Setup.local 文件里面添加一行代码:

+ +
spam spammodule.o
+
+ +

之后, 在 Python 解释器的顶层文件里面执行 make 就好了. 你也可以在 Modules 文件夹里面执行 make, 但是你必须先通过 make Makefile 构建这里的 Makefile. 这个步骤在你改变了 Setup.local 文件之后, 是必须执行的.

+ +

如果你的模块连接了其他库, 那么这些库可以在配置文件统一行后面给加上:

+ +
spam spammodule.o -lX11
+
+ +

在 C 代码里面调用 Python 函数

+ +

目前为止, 我们注意力都集中在从 Python 代码里调用 C 函数. 反过来, 从 C 里面调用 Python 也是很有用的. 特别是在一些支持所谓的回调的库里面. 如果一个 C 接口使用了回调, Python 就需要向 Python 程序员提供一种回调机制. 实现上需要能够在 C 的回调代码里面, 调用 Python 程序提供的回调函数. 你也可以想象其他的应用场景.

+ +

幸运的是, Python 解释器很容易被递归调用, 调用 Python 接口也有一套标准的接口. 这里我不想细说如如何从一个原始字符串调用 Python 解释器, 如果你感兴趣的话, 可以看一看 Modules/main.c-c 命令行选项的实现.

+ +

调用 Python 函数是很容易的. 首先, Python 程序员必须通过某种方式, 传递给你 Python 函数对象, 你应该提供一个函数(或者其他的接口)来完成这项工作. 当函数被调用的时候, 就在全局变量(或者你觉得合适的地方)里面保存一个指向 Python 函数对象的指针(要小心地对它使用 Py_INCREF). 作为例子, 下面的代码可作为模块定义的一部分.

+ +
static PyObject *my_callback = NULL;
+
+static PyObject *
+set_my_callback(PyObject *dummy, PyObject *args)
+{
+  PyObject *tmp;
+  PyObject *result = NULL;
+
+  if (!PyArg_ParseTuple(args, "O:set_callback", &tmp)) {
+    return NULL;
+  }
+
+  if (!PyCallable_Check(tmp)) {
+    PyErr_SetString(PyExc_TypeError, "parameter mast be callable.");
+    return NULL;
+  }
+
+  Py_XINCREF(tmp);         /* Add a reference to new callback */
+  Py_XDECREF(my_callback); /* Dispose of previous callback */
+  my_callback = tmp;       /* Remember new callback */
+
+  /* Boilerplate to return "None" */
+  Py_INCREF(Py_None);
+  result = Py_None;
+
+  return result;
+}
+
+ +

这个函数在向解释器注册的时候, 必须使用 METH_VARGARS 方式. 这个方式在”模块方法表和初始化函数”一节已经介绍过了. PyArg_ParseTuple() 函数和它的参数相关文档在 Extracting Parameters in Extension Functions

+ +

Py_XINCREF()Py_XDECREF() 宏用来增加/减少对象的引用计数, 他们用在 NULL 指针上也是安全的(但是请注意这里的上下文中, tmp 指针不可能是空). 更多关于这两个宏的信息请参考相关文档.

+ +

最后, 当要调用 Python 函数的时候, 使用 C 函数 PyObject_CallObject(). 这个函数接受两个参数, 都指向任意 Python 对象: Python 函数, 还有 Python 参数列表. 参数列表必须是一个元组对象, 长度就是参数的个数. 如果希望不带参数调用 Python 函数, 那么, 参数列表对象就传空值, 或者传一个空的元组对象. 要以一个参数调用它, 传一个单元素元组. 当传给 Py_BuildValue() 函数的格式化字符串里面有以括号扩起来的零个或多个格式化元素时, 它会返回一个元组. 比如像下面这样:

+ +
int arg;
+PyObject *arglist;
+PyObject *result;
+...
+arg = 123;
+...
+/* Time to call the callback */
+arglist = Py_BuildValue("(i)", arg);
+result = PyObject_CallObject(my_callback, arglist);
+Py_DECREF(arglist);
+
+ +

PyObject_CallObject() 返回一个 指向 Python 函数返回值的 Python 对象指针, PyObject_CallObject() 函数对它的参数是所谓的 reference-count-neutral 的. 在例子里面, 会创建一个新的元组作为参数列表传入, 所以后面马上调用 Py_DECREF() 处理了一下.

+ +

PyObject_CallObject() 的返回值是”新的”, 要么它是一个全新的对象, 要么就是一个已经存在了的引用数加一的对象. 因此, 除非你想把它作为一个全局对象, 否则即便你对返回值不感兴趣, 你也应该对它设法调用 Py_DECREF()

+ +

做上面这些处理之前, 检查返回值是不是 NULL 是很重要的. 如果是的话, 那 Python 函数就是被抛出的异常打断的. 如果调用 PyObject_CallObject 的 C 代码是从 Python 里面调用的, 那么它应该返回一个错误来通知它的 Python 调用者, 然后解释器就会打印调用栈, 或者调用处理这个异常的 Python 代码. 如果不想网这么做, 或者无法实现, 产生的异常应该调用 PyErr_Clear() 函数清除. 比如:

+ +
if (result == NULL)
+    return NULL; /* Pass error back */
+...use result...
+Py_DECREF(result);
+
+ +

根据希望使用的 Python 回调函数接口, 你也可以向 PyObject_CallObject 提供一个参数列表, 一些情况下, 参数列表也可以由 Python 程序员通过规定了回调函数的接口提供. 之后参数列表可以被保存或者作为 Python 对象使用. 在其他情况下, 你可能需要自己构建新的元组作为参数列表. 最简单的方式就是调用 Py_BuildValue 函数. 比如说, 我们希望传递一个整型事件码, 可以使用下面的代码:

+ +
PyObject *arglist;
+...
+arglist = Py_BuildValue("(l)", eventcode);
+result = PyObject_CallObject(my_callback, arglist);
+Py_DECREF(arglist);
+if (result == NULL)
+    return NULL; /* Pass error back */
+/* Here maybe use the result */
+Py_DECREF(result);
+
+ +

请注意, Py_DECREF(arglist) 调用发生在调用完成之后, 错误检查之前. 严格来说, 这段代码并不完整, Py_BuildValue 可能会因为内存不够用而返会失败, 这应该要检查.

+ +

你也可以使用 PyObject_Call 函数通过关键字参数来调用函数, 这个函数支持参数和关键字参数, 还以上面的例子, 我们使用 Py_BuildValue 来创建一个字典.

+ +
PyObject *dict;
+...
+dict = Py_BuildValue("{s:i}", "name", val);
+result = PyObject_Call(my_callback, NULL, dict);
+Py_DECREF(dict);
+if (result == NULL) {
+    return NULL; /* Pass error back */
+}
+/* Here maybe use the result */
+Py_DECREF(result);
+
+ +

在扩展函数里面取得参数

+ +

PyArg_ParseTuple 函数具有一下定义:

+ +
int PyArg_ParseTuple(Pyobject *arg, const char *format, ...);
+
+ +

arg 参数必须是一个包含了从 Python 传送给 C 函数参数元素的元组对象. format 参数一定要是一个字符串, 它的语法在文档的解析参数和构建值章节有解释(whose syntax is explained in Parsing arguments and building values in the Python/C API Reference Manual). 余下的参数, 应该是格式化字符串里面对应的类型对象的地址.

+ +
+

请注意 PyArg_ParseTuple() 函数检测 Python 参数具有要求的的类型, 但是它不能检测传递给它的 C 变量地址的有效性, 所以, 如果你在这里犯错误, 那么你的成活可能会崩溃, 或者最少会在内存里面写一些无意义的字节. 所以请小心.

+
+ +
+

请注意所有提供给调用者的 Python 对象, 都是所谓的”借用引用(borrowed references)”, 所以不要自行减少他们的引用数量.

+
+ +

下面是一些示例代码:

+ +
#define PY_SIZE_T_CLEAN /* Make "s#" use Py_ssize_t rather than int. */
+#include <Python.h>
+
+int ok;
+int i, j;
+long k, l;
+const char *s;
+Py_ssize_t size;
+
+/* no arguments */
+/* Python call: f() */
+ok = PyArg_ParseTuple(args, "");
+
+/* a string */
+/* python call: f('hello world') */
+ok = PyArg_ParseTuple(args, "s", &s);
+
+/* two longs and a string */
+/** python call: f(1, 2, 'three') */
+ok = PyArg_ParseTuple(args, "lls", &k, &l, &s);
+
+/* A pair of ints and a string, whose size is also returned */
+/* Possible Python call: f((1, 2), 'three') */
+ok = PyArg_ParseTuple(args, "(ii)s#", &i, &j, &s, &size);
+
+{
+  const char *file;
+  const char *mode = "r";
+  int bufsize = 0;
+
+  /* A string, and optionally another string and an integer */
+  /* Possible Python calls:
+     f('spam')
+     f('spam', 'w')
+     f('spam', 'wb', 100000) */
+  ok = PyArg_ParseTuple(args, "s|si", &file, &mode, &bufsize);
+}
+{
+  int left, top, right, bottom, h, v;
+
+  /* A rectangle and a point */
+  /* Possible Python call:
+     f(((0, 0), (400, 300)), (10, 10)) */
+  ok = PyArg_ParseTuple(args, "((ii)(ii))(ii)", &left, &top, &right, &bottom, &h, &v);
+}
+{
+  Py_complex c;
+
+  /* a complex, also providing a function name for errors */
+  /* Possible Python call: myfunction(1+2j) */
+  ok = PyArg_ParseTuple(args, "D:myfunction", &c);
+}
+
+ +

在扩展里面使用关键字参数

+ +

PyArg_ParseTupleAndKeywords() 函数原型如下:

+ +
int PyArg_ParseTupleAndKeywords(PyObject *arg, PyObject *kwdict,
+                                const char *format, char *kwlist[], ...);
+
+ +

传给它的 argformat 参数意义和传给 PyArg_ParseTuple 的一样. kwdict 参数是 Python 运行时传递给 C 函数作为关键字字典的第三个参数. kwlist 参数应该是一个 NULL 结尾的字符串列表, 名字和格式化字符串里面从左到右相匹配. 执行成功的时候, PyArg_ParseTupleAndKwywords 函数会犯会真, 否则它返回假, 并且会触发一个相应的异常.

+ +

当使用关键字参数时, 里面的嵌套元组不能解析. 传入不在 kwlist 里面的关键字参数会导致 TypeError 异常, 下面是一个 Geoff Philbrick 写的例子:

+ +
#include <Python.h>
+
+static PyObject *
+keywdarg_parrot(PyObject *self, PyObject *args, PyObject *keywds)
+{
+    int voltage;
+    char *state = "a stiff";
+    char *action = "voom";
+    char *type = "Norwegian Blue";
+
+    static char *kwlist[] = {"voltage", "state", "action", "type", NULL};
+
+    if (!PyArg_ParseTupleAndKeywords(args, keywds, "i|sss", kwlist,
+                                     &voltage, &state, &action, &type))
+        return NULL;
+
+    printf("-- This parrot wouldn't %s if you put %i Volts through it.\n",
+           action, voltage);
+    printf("-- Lovely plumage, the %s -- It's %s!\n", type, state);
+
+    Py_RETURN_NONE;
+}
+
+static PyMethodDef keywdarg_methods[] = {
+    /* The cast of the function is necessary since PyCFunction values
+     * only take two PyObject* parameters, and keywdarg_parrot() takes
+     * three.
+     */
+    {"parrot", (PyCFunction)keywdarg_parrot, METH_VARARGS | METH_KEYWORDS,
+     "Print a lovely skit to standard output."},
+    {NULL, NULL, 0, NULL}   /* sentinel */
+};
+
+static struct PyModuleDef keywdargmodule = {
+    PyModuleDef_HEAD_INIT,
+    "keywdarg",
+    NULL,
+    -1,
+    keywdarg_methods
+};
+
+PyMODINIT_FUNC
+PyInit_keywdarg(void)
+{
+    return PyModule_Create(&keywdargmodule);
+}
+
+
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2018/01/24/bitcoin-note.html b/2018/01/24/bitcoin-note.html new file mode 100644 index 0000000..4e49629 --- /dev/null +++ b/2018/01/24/bitcoin-note.html @@ -0,0 +1,1301 @@ + + + +比特币科普 - 挚爱荒原 + + + + + + + + +
+
+
+ +
+ +
+ +
+ +
+ + + +
+

比特币科普

  + + +
+ + + + + +
+
+ + + +
+

历史

+ +

2008 年, 中本聪(名字像日本人, 但是到现在为止我们仍然不知道中本聪到底是谁)发表了一篇文章, 标题是 “Bitcoin:A Peer To Peer Electronic Cash System”, 在这篇文章里, 他描述比特币基本的原理. 这篇文章发表出来以后, 在社区里激起了广大的反响. 2009 年, 基于这个技术的比特币正式诞生.

+ +

比特币诞生之后很长一段时间里, 它都是极客玩家在玩. 2009 年 1 月 12 日, 哈尔·芬尼(芬尼是计算机科学家,比特币先锋)和中本聪之间进行了第一笔比特币交易. 2010 年 5 月 22 日, 有人用一万个比特币点了一份价值 $25 的批萨外卖…. 之后的几年时间里, 越来越多的人接触到这个新奇的东西, 越来越多的人开始接受它, 它的价格也被炒的越来越高. 最近一段时间的均价在 $11000 左右(本周暴跌, 我有杠杆还重仓…真是投资砖家:disappointed:).

+ + + +

必须了解的常识

+ +

比特币可以理解成有一下几个要素构成:

+ +
    +
  1. 一个去中心化的点对点网络(比特币协议)
  2. +
  3. 一个公共的交易账簿(区块链)
  4. +
  5. 一个去中心化的数学的和确定性的货币发行(分布式挖矿)
  6. +
  7. 一个去中心化的交易验证系统(交易脚本)
  8. +
+ +

这几点其实一点都不高端, 但是这几个朴素的元素设计在一起, 产生了可能是未来最具颠覆性的技术. 这里, 个人理解人工智能解决的是很多事我们不想干, 想让机器去干的问题. 而基于区块链的技术, 则解决了人与人之间的信任问题.

+ +

P2P 网络

+ +

听上去很高端的一个词, 我们其实很早之前就在用类似的技术了. 如果你用过 BT 下载(电驴, 比特彗星都是这一类), 没用过没关系, 还有个迅雷, 这个你肯定用过. 他们背后就用到了点对点(P2P)网络. 简单来说, 这个网络就是图上这样:

+ +

P2P Network

+ +

图中, 所有电脑的地位都是平等的, 有信息过来大家可以相互发送, 最终所有人都是可以收到消息的, 这种信息传递机制, 当你在支付了一比比特币之后, 可以迅速的经由这些机器, 广播到整个网络上去. 每个机器收到你的支付消息之后(当然也可收到其他人的), 就会通过一场计算数学题的比赛, 选出一个优胜者, 来把你的交易记录到账本上(参加这个比赛的人就是矿工, 记录过程就是挖矿, 账本就是区块链).

+ +

区块链

+ +

这个名词现在已经被炒上了天, 实际上它就是一个一串数据块. 可以把它想想成账本, 而其中一块数据可以想想成账本的一页. 每页的结构简单描述如下:

+ +

img

+ +

这几个结构里面, 交易字段记录了正真的帐目信息. 最重要的一部分是区块头那个字段, 你可以把这部分想想成书页用来写备注的侧栏, 它里面包含了很多非常重要的数据:

+ +

img

+ +

Nonce 是这一轮数学比赛里面获胜的那个答案. 时间戳是这些帐目被记录上去的时间(不是交易生成的时间, 是记账时间). 父区块哈希值是用 Nonce 和时间戳通过一定的密码学算法生成的, 如果你想改上一页帐目数据, 就会导致这个摘要改变, 到时候你就会发现, 这个账本前一页和后一页对不上了, 这也是比特币交易不可更改的基础. 其他字段不够核心, 不说了.

+ +

挖矿

+ +

这里的挖矿不是让你去山西下煤窑. 计算机挖矿的本质是做一些数学运算, 但是这种运算, 除了一个一个数去试之外, 没有什么好办法可以解决. 因为没有捷径, 所以为了挖矿, 要消耗很多电力, 当然为了能比别人算得块, 大家也会不断升级自己的机器. 挖矿并不是没有报酬的, 要不傻子才每天开着一堆机器轰轰轰的浪费国家的电. 比特币世界里, 通过挖矿你可以发行新的比特币(在中国这个是中央银行才有的权力), 你自己新发行出来的币, 当然是给到你的钱包里的. 除了可以发行新币之外, 还可以收取手续费. 手续费可以看作是支付发起人对矿工的打赏, 让矿工们更有动力来给自己的交易打包/验证.

+ +

交易脚本

+ +

比特币里面其实是没有账户的概念的, 有多少比特币也只是在数字上体现. 那么你怎么才能花费掉属于你的比特币呢?

+ +

比特币里和账户接近的概念叫做比特币钱包地址, 上面说的记账, 其实记录的是比特币数字在一个地址一个地址之间的流动.

+ +

这个地址可以理解成一个没有上锁的锁. 别人给你发送一笔钱, 实际上是发送到这个地址上了, 效果可以理解成, 这笔钱被你的这个锁锁上了, 除了你之外, 没有人能打开这个锁. 那么问题来了, 你的钥匙是什么? 放在哪里?

+ +

你的钥匙, 实际上是一串数字, 这个数字的长度需要用 256 个二进制位来表示(所以它的大小最大是 2 的 256 次方, 这是个天文数字, 超过了宇宙中原子的总数). 别人如果能很容易找到你的这个数字, 那他就可以花掉你的钱, 不过幸运的是, 目前谁都算不出来. 这个数字以文件形式存在放你电脑上, 在比特币世界里, 有一种备份叫做冷备份, 意思就是把这一串数字抄下来, 写在纸上, 然后删掉自己的私钥, 这样即便是有人攻击了/偷了你的电脑, 他也花不了你的钱.

+ +

交易脚本可以理解为一组动作, 这组动作包含解密, 支付等流程. 只有在你的私钥正确的情况下, 你才能执行成功这一连串动作, 从而花掉你的钱.

+ +

总结

+ +

上面是为了给不了解技术细节的人准备的, 我呢, 也不经常写科普文, 再加上这个短文是今天一晚上凑出来的, 估计很多地方稀里糊涂也没描述明白, 不清楚的地方, 可以直接问我.

+ +

现实世界里比特币交易是一个非常精细/精密的过程, 上面的描述只是一个大概轮廓, 更多的细节, 有兴趣可以到网路上找资料, 现在网路上关于比特币的资料还是很全的.

+
+ + +
+ + +
+
+ + +
+
+ +
+
+ +
+ + + + + + +
+ + + + diff --git a/2022/07/09/backtrader-quick-start.html b/2022/07/09/backtrader-quick-start.html new file mode 100644 index 0000000..ad46404 --- /dev/null +++ b/2022/07/09/backtrader-quick-start.html @@ -0,0 +1,2522 @@ + + + +Backtrader 学习系列 - QuickStart - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Backtrader 学习系列 - QuickStart

  + +
+
+ + +
+

参考原文: https://www.backtrader.com/docu/quickstart/quickstart

+ +

IMPORTANT: The data files used in the quickstart guide are updated from time to time, which means that the adjusted close changes and with it the close (and the other components). That means that the actual output may be different to what was put in the documentation at the time of writing.

+
+ +

平台使用

+ +

首先粗略地解释使用 backtrader 时的 2 个基本概念, 然后来看一系列从空跑直到几乎完整的策略的例子.

+ +
    +
  1. +

    Lines

    + +

    Data Feeds, Indicators 以及 Strategy 拥有 lines. 所谓的 line 就是一些列的点组成的线, 通常 Data Feed 有以下几条 line: Open, High, Low, Close, Volume, OpenInterest. 举例来说 Open 点随时间移动可以组成一条 line, 其他几个指标也是如此. 因此一个 Data Feed 通常有 6 条 line. 考虑 DateTime 的话(DateTime 是 backtrader DataFeed 的索引), 那就有 7 条 line.

    +
  2. +
  3. +

    索引 0

    + +

    当访问 line 上面的数据时, 当前值得索引是 0, 访问上一个值使用索引 -1. Python 语言中 -1 一般用来表示一个可迭代对象的最后一个值, 我们这里用 -1 来表示最后一个 output 值(这里的 output 应该就是已经处理过的数据列表)

    +
  4. +
+ +

现在想像一下我们在策略初始化的时候创建一条移动平均线(Simple Moving Average):

+ + + +
self.sma = SimpleMovingAverage(.....)
+
+ +

访问当前值的最简单方式是:

+ +
av = self.sma[0]
+
+ +

因为 0 可以唯一表示当前正在处理的数据, 因此无需知道当前已经处理了多少 bar/minute/day/month.

+ +

遵从 pythonic 传统, 最后一个输出值可以通过 -1 获得:

+ +
previous_value = self.sma[-1]
+
+ +

当然更早的数据可以通过 -2, -3….来获取.

+ +

实例: 从 0 到 100

+ +

基本设置

+ +

简单跑一下下面的代码:

+ +
from __future__ import (absolute_import, division, print_function, unicode_literals)
+
+import backtrader as bt
+
+if __name__ == '__main__':
+    cerebro = bt.Cerebro()
+
+    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
+
+    cerebro.run()
+
+    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
+
+ +

上面的代码跑完之后, 可以得到如下结果输出:

+ +
Starting Portfolio Value: 10000.00
+Final Portfolio Value: 10000.00
+
+ +

在这个例子中:

+ +
    +
  • 引入了 backtrader 库
  • +
  • 创建了 cerebro 引擎
  • +
  • cerebro 被告知执行(遍历数据)
  • +
  • 输出了最终的结果
  • +
+ +

这里指出来一些代码明面上看不到的东西:

+ +
    +
  • cerebro 引擎在背地里创建了一个 broker 对象(用来表示券商).
  • +
  • 实例在开始执行的时候有一些初始资金(10000)
  • +
+ +

broker 是表示券商的对象, 如果不显式设置的话, 就会使用内置默认的券商实现.

+ +

设置现金

+ +

在金融世界中只有 loser 才会用 1 万块入场. 让我们改一下资金额度然后重跑一下例子.

+ +
--- a/x.py
++++ b/x.py
+@@ -4,6 +4,7 @@ import backtrader as bt
+
+ if __name__ == '__main__':
+     cerebro = bt.Cerebro()
++    cerebro.broker.setcash(100000.0)
+
+     print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
+
+ +

上面例子执行过后, 得到如下输出:

+ +
Starting Portfolio Value: 1000000.00
+Final Portfolio Value: 1000000.00
+
+ +

任务完成, 让我们更进一步.

+ +

添加 Data Feed

+ +

我们的目的是通过自动化策略对数据的处理, 使得现金增加. 接下来我们在程序中增加一些数据:

+ +
from __future__ import (absolute_import, division, print_function, unicode_literals)
+
+import datetime  # For datetime objects
+import os.path  # To manage paths
+import sys  # To find out the script name (in argv[0])
+
+# Import the backtrader platform
+import backtrader as bt
+
+if __name__ == '__main__':
+    # Create a cerebro entity
+    cerebro = bt.Cerebro()
+
+    # Datas are in a subfolder of the samples. Need to find where the script is
+    # because it could have been called from anywhere
+    modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
+    datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')
+
+    # Create a Data Feed
+    data = bt.feeds.YahooFinanceCSVData(
+        dataname=datapath,
+        # Do not pass values before this date
+        fromdate=datetime.datetime(2000, 1, 1),
+        # Do not pass values after this date
+        todate=datetime.datetime(2000, 12, 31),
+        reverse=False)
+
+    # Add the Data Feed to Cerebro
+    cerebro.adddata(data)
+
+    # Set our desired cash start
+    cerebro.broker.setcash(100000.0)
+
+    # Print out the starting conditions
+    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
+
+    # Run over everything
+    cerebro.run()
+
+    # Print out the final result
+    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
+
+ +

执行后输出:

+ +
Starting Portfolio Value: 1000000.00
+Final Portfolio Value: 1000000.00
+
+ +

代码量略有增加, 因为我们添加了:

+ +
    +
  • 指定示例数据文件的位置
  • +
  • 使用 datetime 对象来过滤数据
  • +
  • 将数据添加到 cerebro
  • +
+ +

因为依然没有交易策略, 上面的操作暂时不会导致输出改变.

+ +
+

Yahoo Online 以日期降序发送 CSV 数据, 这不是标准约定. reversed=True 参数考虑了文件中的 CSV 数据已被反转以标准的预期日期升序.

+
+ +

第一个策略

+ +

第一个策略我们简单地打印每天的收盘价格.

+ +

DataSeries(Data Feed 中的基础类)对象具有别名用于访问众所周知的 OHLC 数值, 这应该可以简化我们打印逻辑.

+ +
--- x.py	2022-07-09 15:55:44.628483147 +0800
++++ y.py	2022-07-09 15:59:15.900063578 +0800
+@@ -7,10 +7,31 @@
+ # Import the backtrader platform
+ import backtrader as bt
+
++
++# Create a Stratey
++class TestStrategy(bt.Strategy):
++
++    def log(self, txt, dt=None):
++        ''' Logging function for this strategy'''
++        dt = dt or self.datas[0].datetime.date(0)
++        print('%s, %s' % (dt.isoformat(), txt))
++
++    def __init__(self):
++        # Keep a reference to the "close" line in the data[0] dataseries
++        self.dataclose = self.datas[0].close
++
++    def next(self):
++        # Simply log the closing price of the series from the reference
++        self.log('Close, %.2f' % self.dataclose[0])
++
++
+ if __name__ == '__main__':
+     # Create a cerebro entity
+     cerebro = bt.Cerebro()
+
++    # Add a strategy
++    cerebro.addstrategy(TestStrategy)
++
+     # Datas are in a subfolder of the samples. Need to find where the script is
+     # because it could have been called from anywhere
+     modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
+
+ +

执行之后我们可以得到以下输出:

+ +
Starting Portfolio Value: 100000.00
+2000-01-03T00:00:00, Close, 27.85
+2000-01-04T00:00:00, Close, 25.39
+2000-01-05T00:00:00, Close, 24.05
+...
+...
+...
+2000-12-26T00:00:00, Close, 29.17
+2000-12-27T00:00:00, Close, 28.94
+2000-12-28T00:00:00, Close, 29.29
+2000-12-29T00:00:00, Close, 27.41
+Final Portfolio Value: 100000.00
+
+ +

解释一下:

+ +
    +
  • +

    init 被调用时, 策略已经拥有平台中存在的数据列表.

    + +

    这个数据是一个标准的 Python 列表, 数据可以通过他们的插入顺序访问到. self.datas[0] 将会作为默认的系统时钟(The first data in the list self.datas[0] is the default data for trading operations and to keep all strategy elements synchronized (it’s the system clock)).

    +
  • +
  • +

    self.dataclose = self.datas[0].close 是对 Close line 的引用. 将来在策略中可以通过它访问收盘价.

    +
  • +
  • +

    策略的 next 方法将会对系统时钟上的每个 bar 都做一次调用.

    + +

    如果一些指标需要几个 bar 计算得出, 那么首次调用会发生在第一个能指标产出的 bar 上. 后续这块有详细介绍.

    +
  • +
+ +

为策略添加一些逻辑

+ +

如果价格连续下跌 3 个交易日, 那就买买买!!!

+ +
--- y.py	2022-07-09 16:20:53.612313313 +0800
++++ x.py	2022-07-09 16:22:25.128408269 +0800
+@@ -24,6 +24,16 @@
+         # Simply log the closing price of the series from the reference
+         self.log('Close, %.2f' % self.dataclose[0])
+
++        if self.dataclose[0] < self.dataclose[-1]:
++            # current close less than previous close
++
++            if self.dataclose[-1] < self.dataclose[-2]:
++                # previous close less than the previous close
++
++                # BUY, BUY, BUY!!! (with all possible default parameters)
++                self.log('BUY CREATE, %.2f' % self.dataclose[0])
++                self.buy()
++
+
+ if __name__ == '__main__':
+     # Create a cerebro entity
+
+ +

上面的例子执行完成之后输出:

+ +
Starting Portfolio Value: 100000.00
+2000-01-03, Close, 27.85
+2000-01-04, Close, 25.39
+2000-01-05, Close, 24.05
+2000-01-05, BUY CREATE, 24.05
+2000-01-06, Close, 22.63
+2000-01-06, BUY CREATE, 22.63
+2000-01-07, Close, 24.37
+...
+...
+...
+2000-12-20, BUY CREATE, 26.88
+2000-12-21, Close, 27.82
+2000-12-22, Close, 30.06
+2000-12-26, Close, 29.17
+2000-12-27, Close, 28.94
+2000-12-27, BUY CREATE, 28.94
+2000-12-28, Close, 29.29
+2000-12-29, Close, 27.41
+Final Portfolio Value: 99725.08
+
+ +

例子中发出了一些买入指令, 我们的资产组合也出现了下降. 这里我们显然漏掉了一些事情:

+ +
    +
  • 订单只是创建了, 但是不知道订单是否执行, 也不知道订单的执行时间和价格. 接下来的例子中将会监听订单的状态通知.
  • +
+ +

好奇的读者可能会问购买了多少股, 具体是什么资产以及如何执行的订单. 在可能的情况下(本例是这样), 平台填补了:

+ +
    +
  • +

    在不指定资产(目标股票)的时候, 主数据(self.datas[0], 也即系统时钟)就是目标数据.

    +
  • +
  • +

    数量使用固大小的 sizer 在幕后提供, 默认为 1. 后面的示例中我们修修改它.

    +
  • +
  • +

    订单以 市价 执行, Broker 使用下一根 bar 的开盘价执行此项, 因为这是当前 bar 之后的第一个价格变动.

    +
  • +
  • +

    订单在没有任何确认机制(以后会补上)的情况下执行

    +
  • +
+ +

别光顾着买, 我们还要卖!

+ +
    +
  • +

    Strategy 对象提供了对默认 Data Feed 的仓位(position)属性的访问

    +
  • +
  • +

    buysell 方法返回创建(created, 尚未执行)的订单

    +
  • +
  • +

    订单状态的更改将通过通知(notify)方法通知策略

    +
  • +
+ +

我们的卖出策略也很简单:

+ +

无论盈利与否, 都在 5 个 bar 之后(也即底 6 个 bar)的时候卖出. 请注意用 bar 的数量可以代表时间(1 分钟/小时/天/周/月等等)周期.

+ +

在入市之前, 只允许买单(buy).

+ +
+

next 方法收不到 bar 的 index 参数. 获取第五根这个信息可以通过更 pythonic 的方式: 对 line 对象调用 len 方法, 它会告诉你 line 的长度, 只需要在变量中记录下来操作时刻的线长度, 然后和当前长度比较就知道是否是第五根 bar.

+
+ +
--- y.py	2022-07-09 16:41:34.389643481 +0800
++++ x.py	2022-07-09 16:42:35.663290037 +0800
+@@ -20,19 +20,63 @@
+         # Keep a reference to the "close" line in the data[0] dataseries
+         self.dataclose = self.datas[0].close
+
++        # To keep track of pending orders
++        self.order = None
++
++    def notify_order(self, order):
++        if order.status in [order.Submitted, order.Accepted]:
++            # Buy/Sell order submitted/accepted to/by broker - Nothing to do
++            return
++
++        # Check if an order has been completed
++        # Attention: broker could reject order if not enough cash
++        if order.status in [order.Completed]:
++            if order.isbuy():
++                self.log('BUY EXECUTED, %.2f' % order.executed.price)
++            elif order.issell():
++                self.log('SELL EXECUTED, %.2f' % order.executed.price)
++
++            self.bar_executed = len(self)
++
++        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
++            self.log('Order Canceled/Margin/Rejected')
++
++        # Write down: no pending order
++        self.order = None
++
+     def next(self):
+         # Simply log the closing price of the series from the reference
+         self.log('Close, %.2f' % self.dataclose[0])
+
+-        if self.dataclose[0] < self.dataclose[-1]:
+-            # current close less than previous close
++        # Check if an order is pending ... if yes, we cannot send a 2nd one
++        if self.order:
++            return
++
++        # Check if we are in the market
++        if not self.position:
++
++            # Not yet ... we MIGHT BUY if ...
++            if self.dataclose[0] < self.dataclose[-1]:
++                # current close less than previous close
++
++                if self.dataclose[-1] < self.dataclose[-2]:
++                    # previous close less than the previous close
++
++                    # BUY, BUY, BUY!!! (with default parameters)
++                    self.log('BUY CREATE, %.2f' % self.dataclose[0])
++
++                    # Keep track of the created order to avoid a 2nd order
++                    self.order = self.buy()
++
++        else:
+
+-            if self.dataclose[-1] < self.dataclose[-2]:
+-                # previous close less than the previous close
++            # Already in the market ... we might sell
++            if len(self) >= (self.bar_executed + 5):
++                # SELL, SELL, SELL!!! (with all possible default parameters)
++                self.log('SELL CREATE, %.2f' % self.dataclose[0])
+
+-                # BUY, BUY, BUY!!! (with all possible default parameters)
+-                self.log('BUY CREATE, %.2f' % self.dataclose[0])
+-                self.buy()
++                # Keep track of the created order to avoid a 2nd order
++                self.order = self.sell()
+
+
+ if __name__ == '__main__':
+
+ +

上面的脚本执行完成之后, 得到以下输出:

+ +
Starting Portfolio Value: 100000.00
+2000-01-03T00:00:00, Close, 27.85
+2000-01-04T00:00:00, Close, 25.39
+2000-01-05T00:00:00, Close, 24.05
+2000-01-05T00:00:00, BUY CREATE, 24.05
+2000-01-06T00:00:00, BUY EXECUTED, 23.61
+2000-01-06T00:00:00, Close, 22.63
+2000-01-07T00:00:00, Close, 24.37
+2000-01-10T00:00:00, Close, 27.29
+2000-01-11T00:00:00, Close, 26.49
+2000-01-12T00:00:00, Close, 24.90
+2000-01-13T00:00:00, Close, 24.77
+2000-01-13T00:00:00, SELL CREATE, 24.77
+2000-01-14T00:00:00, SELL EXECUTED, 25.70
+2000-01-14T00:00:00, Close, 25.18
+...
+...
+...
+2000-12-15T00:00:00, SELL CREATE, 26.93
+2000-12-18T00:00:00, SELL EXECUTED, 28.29
+2000-12-18T00:00:00, Close, 30.18
+2000-12-19T00:00:00, Close, 28.88
+2000-12-20T00:00:00, Close, 26.88
+2000-12-20T00:00:00, BUY CREATE, 26.88
+2000-12-21T00:00:00, BUY EXECUTED, 26.23
+2000-12-21T00:00:00, Close, 27.82
+2000-12-22T00:00:00, Close, 30.06
+2000-12-26T00:00:00, Close, 29.17
+2000-12-27T00:00:00, Close, 28.94
+2000-12-28T00:00:00, Close, 29.29
+2000-12-29T00:00:00, Close, 27.41
+2000-12-29T00:00:00, SELL CREATE, 27.41
+Final Portfolio Value: 100018.53
+
+ +

可以看到, 结束的时候我们的钱变多了.

+ +

一般券商会收取佣金(commission), 我们买入卖出的佣金费率都设置到 0.1%. 只需一行代码即可完成.

+ +
--- x.py	2022-07-09 22:25:52.204778015 +0800
++++ y.py	2022-07-09 22:25:12.029415237 +0800
+@@ -20,8 +20,10 @@
+         # Keep a reference to the "close" line in the data[0] dataseries
+         self.dataclose = self.datas[0].close
+
+-        # To keep track of pending orders
++        # To keep track of pending orders and buy price/commission
+         self.order = None
++        self.buyprice = None
++        self.buycomm = None
+
+     def notify_order(self, order):
+         if order.status in [order.Submitted, order.Accepted]:
+@@ -32,18 +34,28 @@
+         # Attention: broker could reject order if not enough cash
+         if order.status in [order.Completed]:
+             if order.isbuy():
+-                self.log('BUY EXECUTED, %.2f' % order.executed.price)
+-            elif order.issell():
+-                self.log('SELL EXECUTED, %.2f' % order.executed.price)
++                self.log('BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
++                         (order.executed.price, order.executed.value, order.executed.comm))
++
++                self.buyprice = order.executed.price
++                self.buycomm = order.executed.comm
++            else:  # Sell
++                self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
++                         (order.executed.price, order.executed.value, order.executed.comm))
+
+             self.bar_executed = len(self)
+
+         elif order.status in [order.Canceled, order.Margin, order.Rejected]:
+             self.log('Order Canceled/Margin/Rejected')
+
+-        # Write down: no pending order
+         self.order = None
+
++    def notify_trade(self, trade):
++        if not trade.isclosed:
++            return
++
++        self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' % (trade.pnl, trade.pnlcomm))
++
+     def next(self):
+         # Simply log the closing price of the series from the reference
+         self.log('Close, %.2f' % self.dataclose[0])
+@@ -107,6 +119,9 @@
+     # Set our desired cash start
+     cerebro.broker.setcash(100000.0)
+
++    # Set the commission - 0.1% ... divide by 100 to remove the %
++    cerebro.broker.setcommission(commission=0.001)
++
+     # Print out the starting conditions
+     print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
+
+ +

运行脚本, 看一下在有佣金情况下的收益:

+ +
Starting Portfolio Value: 100000.00
+2000-01-03T00:00:00, Close, 27.85
+2000-01-04T00:00:00, Close, 25.39
+2000-01-05T00:00:00, Close, 24.05
+2000-01-05T00:00:00, BUY CREATE, 24.05
+2000-01-06T00:00:00, BUY EXECUTED, Price: 23.61, Cost: 23.61, Commission 0.02
+2000-01-06T00:00:00, Close, 22.63
+2000-01-07T00:00:00, Close, 24.37
+2000-01-10T00:00:00, Close, 27.29
+2000-01-11T00:00:00, Close, 26.49
+2000-01-12T00:00:00, Close, 24.90
+2000-01-13T00:00:00, Close, 24.77
+2000-01-13T00:00:00, SELL CREATE, 24.77
+2000-01-14T00:00:00, SELL EXECUTED, Price: 25.70, Cost: 25.70, Commission 0.03
+2000-01-14T00:00:00, OPERATION PROFIT, GROSS 2.09, NET 2.04
+2000-01-14T00:00:00, Close, 25.18
+...
+...
+...
+2000-12-15T00:00:00, SELL CREATE, 26.93
+2000-12-18T00:00:00, SELL EXECUTED, Price: 28.29, Cost: 28.29, Commission 0.03
+2000-12-18T00:00:00, OPERATION PROFIT, GROSS -0.06, NET -0.12
+2000-12-18T00:00:00, Close, 30.18
+2000-12-19T00:00:00, Close, 28.88
+2000-12-20T00:00:00, Close, 26.88
+2000-12-20T00:00:00, BUY CREATE, 26.88
+2000-12-21T00:00:00, BUY EXECUTED, Price: 26.23, Cost: 26.23, Commission 0.03
+2000-12-21T00:00:00, Close, 27.82
+2000-12-22T00:00:00, Close, 30.06
+2000-12-26T00:00:00, Close, 29.17
+2000-12-27T00:00:00, Close, 28.94
+2000-12-28T00:00:00, Close, 29.29
+2000-12-29T00:00:00, Close, 27.41
+2000-12-29T00:00:00, SELL CREATE, 27.41
+Final Portfolio Value: 100016.98
+
+ +

数据显示依然是盈利的. 我们过滤出来 “OPERATION PROFIT” 相关数据单独看一下:

+ +
2000-01-14T00:00:00, OPERATION PROFIT, GROSS 2.09, NET 2.04
+2000-02-07T00:00:00, OPERATION PROFIT, GROSS 3.68, NET 3.63
+2000-02-28T00:00:00, OPERATION PROFIT, GROSS 4.48, NET 4.42
+2000-03-13T00:00:00, OPERATION PROFIT, GROSS 3.48, NET 3.41
+2000-03-22T00:00:00, OPERATION PROFIT, GROSS -0.41, NET -0.49
+2000-04-07T00:00:00, OPERATION PROFIT, GROSS 2.45, NET 2.37
+2000-04-20T00:00:00, OPERATION PROFIT, GROSS -1.95, NET -2.02
+2000-05-02T00:00:00, OPERATION PROFIT, GROSS 5.46, NET 5.39
+2000-05-11T00:00:00, OPERATION PROFIT, GROSS -3.74, NET -3.81
+2000-05-30T00:00:00, OPERATION PROFIT, GROSS -1.46, NET -1.53
+2000-07-05T00:00:00, OPERATION PROFIT, GROSS -1.62, NET -1.69
+2000-07-14T00:00:00, OPERATION PROFIT, GROSS 2.08, NET 2.01
+2000-07-28T00:00:00, OPERATION PROFIT, GROSS 0.14, NET 0.07
+2000-08-08T00:00:00, OPERATION PROFIT, GROSS 4.36, NET 4.29
+2000-08-21T00:00:00, OPERATION PROFIT, GROSS 1.03, NET 0.95
+2000-09-15T00:00:00, OPERATION PROFIT, GROSS -4.26, NET -4.34
+2000-09-27T00:00:00, OPERATION PROFIT, GROSS 1.29, NET 1.22
+2000-10-13T00:00:00, OPERATION PROFIT, GROSS -2.98, NET -3.04
+2000-10-26T00:00:00, OPERATION PROFIT, GROSS 3.01, NET 2.95
+2000-11-06T00:00:00, OPERATION PROFIT, GROSS -3.59, NET -3.65
+2000-11-16T00:00:00, OPERATION PROFIT, GROSS 1.28, NET 1.23
+2000-12-01T00:00:00, OPERATION PROFIT, GROSS 2.59, NET 2.54
+2000-12-18T00:00:00, OPERATION PROFIT, GROSS -0.06, NET -0.12
+
+ +

把净值(NET)加起来, 得到 15.83, 显然这个上面执行结果中的最终金额(100016.98)报告的收益 16.98 不符.

+ +

这里实际上并没有出错, 而是 15.83 是钱袋子里面的净利润, 我们在 29 号还有一笔卖出操作(2000-12-29T00:00:00, SELL CREATE, 27.41), 这个操作已经提交, 但是却并没有执行.

+ +

券商计算的价值是包含了以 2000-12-29 的收盘价成交的交易数据. 实际上策略执行订单的时候是在下一个交易日执行, 也就是 2001-01-02 日, 如果扩展 Data Feed 范围包含该日期, 我们会看到以下输出:

+ +
2001-01-02T00:00:00, SELL EXECUTED, Price: 27.87, Cost: 27.87, Commission 0.03
+2001-01-02T00:00:00, OPERATION PROFIT, GROSS 1.64, NET 1.59
+2001-01-02T00:00:00, Close, 24.87
+2001-01-02T00:00:00, BUY CREATE, 24.87
+Final Portfolio Value: 100017.41
+
+ +

在 2001-01-02 日执行了卖出操作, 此时把 OPERATION PROFIT 里面的利润再次相加(15.83 + 1.59), 得到 17.42. 刨除掉浮点数的精度损失, 现在结果就对得上了.

+ +

定制策略 - Parameters

+ +

在策略中对某些值进行硬编码有点不切实际, 并且需要更改它们的时候也会比较费事, 此时可以使用参数(Parameters).

+ +

参数的定义很简单, 看起来像这样:

+ +
params = (
+    ('myparam', 27),
+    ('exitbars', 5),
+)
+
+ +

在使用策略的时候, 可以将参数定制化:

+ +
# Add a strategy
+cerebro.addstrategy(TestStrategy, myparam=20, exitbars=7)
+
+ +

在策略中使用参数也很简单, 他们都被保存在 params 属性中. 比方说我们想设置交易数量, 我们可以再 init 的时候给 sizer 设置参数如下:

+ +
# Set the sizer stake from the params
+self.sizer.setsizing(self.params.stake)
+
+ +

当然我们也可以在 buy 或者 sell 调用的时候, 设置交易数量:

+ +
self.buy(size=self.params.stake)
+
+ +

也可以把卖出时机参数化:

+ +
# Already in the market ... we might sell
+if len(self) >= (self.bar_executed + self.params.exitbars):
+
+ +

上面相关改动如下:

+ +
--- y.py	2022-07-09 23:05:30.201865643 +0800
++++ x.py	2022-07-09 23:01:58.712281938 +0800
+@@ -10,6 +10,7 @@
+
+ # Create a Stratey
+ class TestStrategy(bt.Strategy):
++    params = (('exitbars', 5), )
+
+     def log(self, txt, dt=None):
+         ''' Logging function fot this strategy'''
+@@ -83,7 +84,7 @@
+         else:
+
+             # Already in the market ... we might sell
+-            if len(self) >= (self.bar_executed + 5):
++            if len(self) >= (self.bar_executed + self.params.exitbars):
+                 # SELL, SELL, SELL!!! (with all possible default parameters)
+                 self.log('SELL CREATE, %.2f' % self.dataclose[0])
+
+@@ -119,6 +120,9 @@
+     # Set our desired cash start
+     cerebro.broker.setcash(100000.0)
+
++    # Add a FixedSize sizer according to the stake
++    cerebro.addsizer(bt.sizers.FixedSize, stake=10)
++
+     # Set the commission - 0.1% ... divide by 100 to remove the %
+     cerebro.broker.setcommission(commission=0.001)
+
+ +
+

全部代码参考:

+
+ +
from __future__ import (absolute_import, division, print_function,
+                        unicode_literals)
+
+import datetime  # For datetime objects
+import os.path  # To manage paths
+import sys  # To find out the script name (in argv[0])
+
+# Import the backtrader platform
+import backtrader as bt
+
+
+# Create a Stratey
+class TestStrategy(bt.Strategy):
+    params = (
+        ('exitbars', 5),
+    )
+
+    def log(self, txt, dt=None):
+        ''' Logging function fot this strategy'''
+        dt = dt or self.datas[0].datetime.date(0)
+        print('%s, %s' % (dt.isoformat(), txt))
+
+    def __init__(self):
+        # Keep a reference to the "close" line in the data[0] dataseries
+        self.dataclose = self.datas[0].close
+
+        # To keep track of pending orders and buy price/commission
+        self.order = None
+        self.buyprice = None
+        self.buycomm = None
+
+    def notify_order(self, order):
+        if order.status in [order.Submitted, order.Accepted]:
+            # Buy/Sell order submitted/accepted to/by broker - Nothing to do
+            return
+
+        # Check if an order has been completed
+        # Attention: broker could reject order if not enough cash
+        if order.status in [order.Completed]:
+            if order.isbuy():
+                self.log(
+                    'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
+                    (order.executed.price,
+                     order.executed.value,
+                     order.executed.comm))
+
+                self.buyprice = order.executed.price
+                self.buycomm = order.executed.comm
+            else:  # Sell
+                self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
+                         (order.executed.price,
+                          order.executed.value,
+                          order.executed.comm))
+
+            self.bar_executed = len(self)
+
+        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
+            self.log('Order Canceled/Margin/Rejected')
+
+        self.order = None
+
+    def notify_trade(self, trade):
+        if not trade.isclosed:
+            return
+
+        self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
+                 (trade.pnl, trade.pnlcomm))
+
+    def next(self):
+        # Simply log the closing price of the series from the reference
+        self.log('Close, %.2f' % self.dataclose[0])
+
+        # Check if an order is pending ... if yes, we cannot send a 2nd one
+        if self.order:
+            return
+
+        # Check if we are in the market
+        if not self.position:
+
+            # Not yet ... we MIGHT BUY if ...
+            if self.dataclose[0] < self.dataclose[-1]:
+                    # current close less than previous close
+
+                    if self.dataclose[-1] < self.dataclose[-2]:
+                        # previous close less than the previous close
+
+                        # BUY, BUY, BUY!!! (with default parameters)
+                        self.log('BUY CREATE, %.2f' % self.dataclose[0])
+
+                        # Keep track of the created order to avoid a 2nd order
+                        self.order = self.buy()
+
+        else:
+
+            # Already in the market ... we might sell
+            if len(self) >= (self.bar_executed + self.params.exitbars):
+                # SELL, SELL, SELL!!! (with all possible default parameters)
+                self.log('SELL CREATE, %.2f' % self.dataclose[0])
+
+                # Keep track of the created order to avoid a 2nd order
+                self.order = self.sell()
+
+if __name__ == '__main__':
+    # Create a cerebro entity
+    cerebro = bt.Cerebro()
+
+    # Add a strategy
+    cerebro.addstrategy(TestStrategy)
+
+    # Datas are in a subfolder of the samples. Need to find where the script is
+    # because it could have been called from anywhere
+    modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
+    datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')
+
+    # Create a Data Feed
+    data = bt.feeds.YahooFinanceCSVData(
+        dataname=datapath,
+        # Do not pass values before this date
+        fromdate=datetime.datetime(2000, 1, 1),
+        # Do not pass values before this date
+        todate=datetime.datetime(2000, 12, 31),
+        # Do not pass values after this date
+        reverse=False)
+
+    # Add the Data Feed to Cerebro
+    cerebro.adddata(data)
+
+    # Set our desired cash start
+    cerebro.broker.setcash(100000.0)
+
+    # Add a FixedSize sizer according to the stake
+    cerebro.addsizer(bt.sizers.FixedSize, stake=10)
+
+    # Set the commission - 0.1% ... divide by 100 to remove the %
+    cerebro.broker.setcommission(commission=0.001)
+
+    # Print out the starting conditions
+    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
+
+    # Run over everything
+    cerebro.run()
+
+    # Print out the final result
+    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
+
+ +

输出如下:

+ +
Starting Portfolio Value: 100000.00
+2000-01-03T00:00:00, Close, 27.85
+2000-01-04T00:00:00, Close, 25.39
+2000-01-05T00:00:00, Close, 24.05
+2000-01-05T00:00:00, BUY CREATE, 24.05
+2000-01-06T00:00:00, BUY EXECUTED, Size 10, Price: 23.61, Cost: 236.10, Commission 0.24
+2000-01-06T00:00:00, Close, 22.63
+...
+...
+...
+2000-12-20T00:00:00, BUY CREATE, 26.88
+2000-12-21T00:00:00, BUY EXECUTED, Size 10, Price: 26.23, Cost: 262.30, Commission 0.26
+2000-12-21T00:00:00, Close, 27.82
+2000-12-22T00:00:00, Close, 30.06
+2000-12-26T00:00:00, Close, 29.17
+2000-12-27T00:00:00, Close, 28.94
+2000-12-28T00:00:00, Close, 29.29
+2000-12-29T00:00:00, Close, 27.41
+2000-12-29T00:00:00, SELL CREATE, 27.41
+Final Portfolio Value: 100169.80
+
+ +

为了直观显示每次操作数量, 日志里把 size 也打印出来了. 可以看到每次操作数量增加 10 倍, 最后的收益也跟着增加了 10 倍(从 16.98169.80).

+ +

添加指标(indicator)

+ +

本例中我们使用移动平均线(Simple Moving Average)策略.

+ +
    +
  • 如果收盘价大于当前均价, 那么执行市价买入
  • +
  • 如果有持仓, 并且收盘价小于均线, 执行卖出
  • +
  • 操作过程中只允许有一个活跃操作(订单)
  • +
+ +

只需要一点改动即可实现上面的策略, 首先在 init 的时候加入均线指标:

+ +
self.sma = bt.indicators.MovingAverageSimple(self.datas[0], period=self.params.maperiod)
+
+ +

买入卖出的时机也需要微调, 整体代码 diff 如下(现金设置为 1000, 不收佣金):

+ +
--- x.py	2022-07-09 23:18:14.011198706 +0800
++++ y.py	2022-07-09 23:18:41.734396585 +0800
+@@ -10,7 +10,7 @@
+
+ # Create a Stratey
+ class TestStrategy(bt.Strategy):
+-    params = (('exitbars', 5), )
++    params = (('maperiod', 15), )
+
+     def log(self, txt, dt=None):
+         ''' Logging function fot this strategy'''
+@@ -26,6 +26,9 @@
+         self.buyprice = None
+         self.buycomm = None
+
++        # Add a MovingAverageSimple indicator
++        self.sma = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod)
++
+     def notify_order(self, order):
+         if order.status in [order.Submitted, order.Accepted]:
+             # Buy/Sell order submitted/accepted to/by broker - Nothing to do
+@@ -69,22 +72,17 @@
+         if not self.position:
+
+             # Not yet ... we MIGHT BUY if ...
+-            if self.dataclose[0] < self.dataclose[-1]:
+-                # current close less than previous close
+-
+-                if self.dataclose[-1] < self.dataclose[-2]:
+-                    # previous close less than the previous close
++            if self.dataclose[0] > self.sma[0]:
+
+-                    # BUY, BUY, BUY!!! (with default parameters)
+-                    self.log('BUY CREATE, %.2f' % self.dataclose[0])
++                # BUY, BUY, BUY!!! (with all possible default parameters)
++                self.log('BUY CREATE, %.2f' % self.dataclose[0])
+
+-                    # Keep track of the created order to avoid a 2nd order
+-                    self.order = self.buy()
++                # Keep track of the created order to avoid a 2nd order
++                self.order = self.buy()
+
+         else:
+
+-            # Already in the market ... we might sell
+-            if len(self) >= (self.bar_executed + self.params.exitbars):
++            if self.dataclose[0] < self.sma[0]:
+                 # SELL, SELL, SELL!!! (with all possible default parameters)
+                 self.log('SELL CREATE, %.2f' % self.dataclose[0])
+
+@@ -118,13 +116,13 @@
+     cerebro.adddata(data)
+
+     # Set our desired cash start
+-    cerebro.broker.setcash(100000.0)
++    cerebro.broker.setcash(1000.0)
+
+     # Add a FixedSize sizer according to the stake
+     cerebro.addsizer(bt.sizers.FixedSize, stake=10)
+
+-    # Set the commission - 0.1% ... divide by 100 to remove the %
+-    cerebro.broker.setcommission(commission=0.001)
++    # Set the commission
++    cerebro.broker.setcommission(commission=0.0)
+
+     # Print out the starting conditions
+     print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
+
+ +

执行结果如下:

+ +
Starting Portfolio Value: 1000.00
+2000-01-24T00:00:00, Close, 25.55
+2000-01-25T00:00:00, Close, 26.61
+2000-01-25T00:00:00, BUY CREATE, 26.61
+2000-01-26T00:00:00, BUY EXECUTED, Size 10, Price: 26.76, Cost: 267.60, Commission 0.00
+2000-01-26T00:00:00, Close, 25.96
+2000-01-27T00:00:00, Close, 24.43
+2000-01-27T00:00:00, SELL CREATE, 24.43
+2000-01-28T00:00:00, SELL EXECUTED, Size 10, Price: 24.28, Cost: 242.80, Commission 0.00
+2000-01-28T00:00:00, OPERATION PROFIT, GROSS -24.80, NET -24.80
+2000-01-28T00:00:00, Close, 22.34
+2000-01-31T00:00:00, Close, 23.55
+2000-02-01T00:00:00, Close, 25.46
+2000-02-02T00:00:00, Close, 25.61
+2000-02-02T00:00:00, BUY CREATE, 25.61
+2000-02-03T00:00:00, BUY EXECUTED, Size 10, Price: 26.11, Cost: 261.10, Commission 0.00
+...
+...
+...
+2000-12-20T00:00:00, SELL CREATE, 26.88
+2000-12-21T00:00:00, SELL EXECUTED, Size 10, Price: 26.23, Cost: 262.30, Commission 0.00
+2000-12-21T00:00:00, OPERATION PROFIT, GROSS -20.60, NET -20.60
+2000-12-21T00:00:00, Close, 27.82
+2000-12-21T00:00:00, BUY CREATE, 27.82
+2000-12-22T00:00:00, BUY EXECUTED, Size 10, Price: 28.65, Cost: 286.50, Commission 0.00
+2000-12-22T00:00:00, Close, 30.06
+2000-12-26T00:00:00, Close, 29.17
+2000-12-27T00:00:00, Close, 28.94
+2000-12-28T00:00:00, Close, 29.29
+2000-12-29T00:00:00, Close, 27.41
+2000-12-29T00:00:00, SELL CREATE, 27.41
+Final Portfolio Value: 973.90
+
+ +

仔细看日志中的输出, 你会发现第一个输出不再是 2000-01-03, 而是 2000-01-24, 前面的时间去哪里了?

+ +

丢掉的日期其实并没有真的倍丢掉, backtrader 只是适应了一下最新的情况: 有一条移动平均线被加入到了策略中来. 指标需要 N 天来计算第一个输出, 在本例中是 15, 而 2000-01-24 正是第 15 天(第 15 根 bar).

+ +

backtrader 假设只有在指标计算完成的时候才发起第一次 next 调用, 实例的策略中只有一个指标, 如果有多个指标, 那就要等最晚的那个指标有第一个 output 的时候, 才会发起第一次 next 调用.

+ +

观察输出我们看到, 这次的收益实际上是负的, 加指标并没有让我们更赚钱…. 😂

+ +
+

因为浮点数的精度问题, 相同的数据相同的策略可能出现一点点微小的差别.

+
+ +

可视化: 绘制图表

+ +

之前我们只是在每个 bar 打印了相关输出, 但是人类是非常视觉的动物, 这里我们介绍如何把结果绘制出来.

+ +
+

为了画图, 需要安装 matplotlib 库.

+
+ +

backtrader 内置了一个绘图功能, 只需要在 cerebro.run() 之后加一行代码调用即可:

+ +
cerebro.plot()
+
+ +

问了展示在绘图结果中做一些简单的自定义功能, 我们将添加以下内容到图上:

+ +
    +
  • 指数移动平均线(EMA)
  • +
  • 加权移动平均线(WMA)
  • +
  • 慢速随机指标
  • +
  • 异同移动平均线(MACD)
  • +
  • 相对强弱指标(RSI)
  • +
  • 简单移动平均线(SMA)将会随 RSI 指标一起绘制
  • +
  • 平均真实波动范围(AverageTrueRange, ATR), 默认不显示
  • +
+ +

默认这些指标在策略 init 中的代码如下:

+ +
# Indicators for the plotting show
+bt.indicators.ExponentialMovingAverage(self.datas[0], period=25)
+bt.indicators.WeightedMovingAverage(self.datas[0], period=25).subplot = True
+bt.indicators.StochasticSlow(self.datas[0])
+bt.indicators.MACDHisto(self.datas[0])
+rsi = bt.indicators.RSI(self.datas[0])
+bt.indicators.SmoothedMovingAverage(rsi, period=10)
+bt.indicators.ATR(self.datas[0]).plot = False
+
+ +
+

即便这些指标没有显式的加入到策略的成员变量中, 它们也会自动被注册到策略中, 并且会影响调用 next 方法的最小 bar.

+
+ +

代码改动 diff:

+ +
--- x.py	2022-07-10 00:15:40.176306415 +0800
++++ y.py	2022-07-10 00:16:56.484414551 +0800
+@@ -29,6 +29,15 @@
+         # Add a MovingAverageSimple indicator
+         self.sma = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod)
+
++        # Indicators for the plotting show
++        bt.indicators.ExponentialMovingAverage(self.datas[0], period=25)
++        bt.indicators.WeightedMovingAverage(self.datas[0], period=25, subplot=True)
++        bt.indicators.StochasticSlow(self.datas[0])
++        bt.indicators.MACDHisto(self.datas[0])
++        rsi = bt.indicators.RSI(self.datas[0])
++        bt.indicators.SmoothedMovingAverage(rsi, period=10)
++        bt.indicators.ATR(self.datas[0], plot=False)
++
+     def notify_order(self, order):
+         if order.status in [order.Submitted, order.Accepted]:
+             # Buy/Sell order submitted/accepted to/by broker - Nothing to do
+@@ -131,4 +140,7 @@
+     cerebro.run()
+
+     # Print out the final result
+-    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
+\ No newline at end of file
++    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
++
++    # Plot the result
++    cerebro.plot()
+\ No newline at end of file
+
+ +

优化

+ +

在开始绘图示例之前, 我们使用的 SMA 周期是 15, 这个参数实际上是一个策略参数, 可以针对不同的股票不同的市场做定制, 知道找到自己认为最合适的参数值.

+ +
+

There is plenty of literature about Optimization and associated pros and cons. But the advice will always point in the same direction: do not overoptimize. If a trading idea is not sound, optimizing may end producing a positive result which is only valid for the backtested dataset.

+
+ +

接下来的示例中修改了 SMA 的周期, 简单起见任何关于买入卖出的日志也都被移除.

+ +

代码 diff:

+ +
--- x.py	2022-07-10 00:21:03.903780834 +0800
++++ y.py	2022-07-10 00:22:52.294211036 +0800
+@@ -10,12 +10,16 @@
+
+ # Create a Stratey
+ class TestStrategy(bt.Strategy):
+-    params = (('maperiod', 15), )
++    params = (
++        ('maperiod', 15),
++        ('printlog', False),
++    )
+
+-    def log(self, txt, dt=None):
++    def log(self, txt, dt=None, doprint=False):
+         ''' Logging function fot this strategy'''
+-        dt = dt or self.datas[0].datetime.date(0)
+-        print('%s, %s' % (dt.isoformat(), txt))
++        if self.params.printlog or doprint:
++            dt = dt or self.datas[0].datetime.date(0)
++            print('%s, %s' % (dt.isoformat(), txt))
+
+     def __init__(self):
+         # Keep a reference to the "close" line in the data[0] dataseries
+@@ -29,15 +33,6 @@
+         # Add a MovingAverageSimple indicator
+         self.sma = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod)
+
+-        # Indicators for the plotting show
+-        bt.indicators.ExponentialMovingAverage(self.datas[0], period=25)
+-        bt.indicators.WeightedMovingAverage(self.datas[0], period=25, subplot=True)
+-        bt.indicators.StochasticSlow(self.datas[0])
+-        bt.indicators.MACDHisto(self.datas[0])
+-        rsi = bt.indicators.RSI(self.datas[0])
+-        bt.indicators.SmoothedMovingAverage(rsi, period=10)
+-        bt.indicators.ATR(self.datas[0], plot=False)
+-
+     def notify_order(self, order):
+         if order.status in [order.Submitted, order.Accepted]:
+             # Buy/Sell order submitted/accepted to/by broker - Nothing to do
+@@ -98,13 +93,16 @@
+                 # Keep track of the created order to avoid a 2nd order
+                 self.order = self.sell()
+
++    def stop(self):
++        self.log('(MA Period %2d) Ending Value %.2f' % (self.params.maperiod, self.broker.getvalue()), doprint=True)
++
+
+ if __name__ == '__main__':
+     # Create a cerebro entity
+     cerebro = bt.Cerebro()
+
+     # Add a strategy
+-    cerebro.addstrategy(TestStrategy)
++    strats = cerebro.optstrategy(TestStrategy, maperiod=range(10, 31))
+
+     # Datas are in a subfolder of the samples. Need to find where the script is
+     # because it could have been called from anywhere
+@@ -133,14 +131,5 @@
+     # Set the commission
+     cerebro.broker.setcommission(commission=0.0)
+
+-    # Print out the starting conditions
+-    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
+-
+     # Run over everything
+-    cerebro.run()
+-
+-    # Print out the final result
+-    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
+-
+-    # Plot the result
+-    cerebro.plot()
+\ No newline at end of file
++    cerebro.run(maxcpus=1)
+\ No newline at end of file
+
+ +

代码:

+ +
from __future__ import (absolute_import, division, print_function, unicode_literals)
+
+import datetime  # For datetime objects
+import os.path  # To manage paths
+import sys  # To find out the script name (in argv[0])
+
+# Import the backtrader platform
+import backtrader as bt
+
+
+# Create a Stratey
+class TestStrategy(bt.Strategy):
+    params = (
+        ('maperiod', 15),
+        ('printlog', False),
+    )
+
+    def log(self, txt, dt=None, doprint=False):
+        ''' Logging function fot this strategy'''
+        if self.params.printlog or doprint:
+            dt = dt or self.datas[0].datetime.date(0)
+            print('%s, %s' % (dt.isoformat(), txt))
+
+    def __init__(self):
+        # Keep a reference to the "close" line in the data[0] dataseries
+        self.dataclose = self.datas[0].close
+
+        # To keep track of pending orders and buy price/commission
+        self.order = None
+        self.buyprice = None
+        self.buycomm = None
+
+        # Add a MovingAverageSimple indicator
+        self.sma = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod)
+
+    def notify_order(self, order):
+        if order.status in [order.Submitted, order.Accepted]:
+            # Buy/Sell order submitted/accepted to/by broker - Nothing to do
+            return
+
+        # Check if an order has been completed
+        # Attention: broker could reject order if not enough cash
+        if order.status in [order.Completed]:
+            if order.isbuy():
+                self.log('BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
+                         (order.executed.price, order.executed.value, order.executed.comm))
+
+                self.buyprice = order.executed.price
+                self.buycomm = order.executed.comm
+            else:  # Sell
+                self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
+                         (order.executed.price, order.executed.value, order.executed.comm))
+
+            self.bar_executed = len(self)
+
+        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
+            self.log('Order Canceled/Margin/Rejected')
+
+        self.order = None
+
+    def notify_trade(self, trade):
+        if not trade.isclosed:
+            return
+
+        self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' % (trade.pnl, trade.pnlcomm))
+
+    def next(self):
+        # Simply log the closing price of the series from the reference
+        self.log('Close, %.2f' % self.dataclose[0])
+
+        # Check if an order is pending ... if yes, we cannot send a 2nd one
+        if self.order:
+            return
+
+        # Check if we are in the market
+        if not self.position:
+
+            # Not yet ... we MIGHT BUY if ...
+            if self.dataclose[0] > self.sma[0]:
+
+                # BUY, BUY, BUY!!! (with all possible default parameters)
+                self.log('BUY CREATE, %.2f' % self.dataclose[0])
+
+                # Keep track of the created order to avoid a 2nd order
+                self.order = self.buy()
+
+        else:
+
+            if self.dataclose[0] < self.sma[0]:
+                # SELL, SELL, SELL!!! (with all possible default parameters)
+                self.log('SELL CREATE, %.2f' % self.dataclose[0])
+
+                # Keep track of the created order to avoid a 2nd order
+                self.order = self.sell()
+
+    def stop(self):
+        self.log('(MA Period %2d) Ending Value %.2f' % (self.params.maperiod, self.broker.getvalue()), doprint=True)
+
+
+if __name__ == '__main__':
+    # Create a cerebro entity
+    cerebro = bt.Cerebro()
+
+    # Add a strategy
+    strats = cerebro.optstrategy(TestStrategy, maperiod=range(10, 31))
+
+    # Datas are in a subfolder of the samples. Need to find where the script is
+    # because it could have been called from anywhere
+    modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
+    datapath = os.path.join(modpath, 'data/example.csv')
+
+    # Create a Data Feed
+    data = bt.feeds.YahooFinanceCSVData(
+        dataname=datapath,
+        # Do not pass values before this date
+        fromdate=datetime.datetime(2000, 1, 1),
+        # Do not pass values before this date
+        todate=datetime.datetime(2000, 12, 31),
+        # Do not pass values after this date
+        reverse=False)
+
+    # Add the Data Feed to Cerebro
+    cerebro.adddata(data)
+
+    # Set our desired cash start
+    cerebro.broker.setcash(1000.0)
+
+    # Add a FixedSize sizer according to the stake
+    cerebro.addsizer(bt.sizers.FixedSize, stake=10)
+
+    # Set the commission
+    cerebro.broker.setcommission(commission=0.0)
+
+    # Run over everything
+    cerebro.run(maxcpus=1)
+
+ +

这次我们没有 addstrategy 添加策略, 而是使用 optstrategy 来优化策略. 对应的参数也没有传递值, 而是传递了一个范围.

+ +

还加了一个策略钩子函数(stop), 当数据被消费完并且回测完成的时候这个回调就会被调用, 这里它被用来打印最后的账户净值.

+ +

系统将会对范围参数里面的每个值, 都执行一遍策略. 下面是这次执行的输出:

+ +
2000-12-29, (MA Period 10) Ending Value 880.30
+2000-12-29, (MA Period 11) Ending Value 880.00
+2000-12-29, (MA Period 12) Ending Value 830.30
+2000-12-29, (MA Period 13) Ending Value 893.90
+2000-12-29, (MA Period 14) Ending Value 896.90
+2000-12-29, (MA Period 15) Ending Value 973.90
+2000-12-29, (MA Period 16) Ending Value 959.40
+2000-12-29, (MA Period 17) Ending Value 949.80
+2000-12-29, (MA Period 18) Ending Value 1011.90
+2000-12-29, (MA Period 19) Ending Value 1041.90
+2000-12-29, (MA Period 20) Ending Value 1078.00
+2000-12-29, (MA Period 21) Ending Value 1058.80
+2000-12-29, (MA Period 22) Ending Value 1061.50
+2000-12-29, (MA Period 23) Ending Value 1023.00
+2000-12-29, (MA Period 24) Ending Value 1020.10
+2000-12-29, (MA Period 25) Ending Value 1013.30
+2000-12-29, (MA Period 26) Ending Value 998.30
+2000-12-29, (MA Period 27) Ending Value 982.20
+2000-12-29, (MA Period 28) Ending Value 975.70
+2000-12-29, (MA Period 29) Ending Value 983.30
+2000-12-29, (MA Period 30) Ending Value 979.80
+
+ +

最终显示 SMA 周期设置为 20 会比较合理.

+ +
+

这个最优参数只是针对回测数据, 不能代表未来.

+
+ +

结语

+ +

上面 渐进式 的示例显示了如何从一个只有骨架的脚本到一个能正常工作并且可以绘制图表及执行参数优化的交易系统是如何演变的.

+ +

后续可以做更多事情来提高胜率:

+ +
    +
  • 自定义指标. 事实上, 创建指标很容易(甚至绘制它们也很容易)
  • +
  • Sizers. 对许多人来说, 资金管理是成功的关键
  • +
  • 订单类型(限价, 止损, 止损限价) - limit, stop, stoplimit
  • +
  • 其他
  • +
+ +

backtrader 的文档对相关部分有详细介绍.

+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2022/08/13/shift-register.html b/2022/08/13/shift-register.html new file mode 100644 index 0000000..5c1e608 --- /dev/null +++ b/2022/08/13/shift-register.html @@ -0,0 +1,2049 @@ + + + +移位寄存器 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

移位寄存器

  + +
+
+ + +
+

英文原文: https://dronebotworkshop.com/shift-registers/

+
+ +

I’ve got a few shifty characters with me today but don’t worry, I’ll show you how to control them and expand the capabilities of your Arduino. And, as a bonus, we’ll build a fancy LED light display.

+ +

介绍

+ +

Today we will work with a couple of basic electronics “building blocks”, shift registers. These handy devices are used for all sorts of purposes like data conversion, buffering and storage, but today we will be seeing how they can also be used to expand the number of digital I/O ports on an Arduino or other microcontrollers.

+ +

+ + + +

By learning to use shift registers you’ll be adding another handy tool to your designers’ toolkit

+ +

Expand your Arduino

+ +

Arduino’s have a number of digital I/O ports already, in fact, the Arduino Mega 250 boasts 54 digital I/O pins plus another 16 analog inputs that can double as digital I/O pins. So with 70 potential I/O pins you don’t usually have a need for more.

+ +

But sometimes you actually do need more.

+ +

Take the familiar “LED Cube”. A cube with a 4x4x4 dimension would require 64 LEDs, within the capability of an Arduino Mega 2560 if you “borrow” a few analog pins. But you’re nearly at the limit.

+ +

If you wanted to expand to a 5x5x5 cube then you’re out of luck, you’ll need 125 LEDs for that and you can’t control them individually with one Arduino.

+ +

Even a 4x4x4 cube using standard (i.e. non-addressable) RGB LEDs would put you past the limit.

+ +

There are many ways to solve these issues, including running the LEDs ina matrix or using a shift register. The shift register will allow you to address a large number of LEDs using only a few Arduino I/O pins.

+ +

There are other times when you have a lot of sensors, displays or other I/O devices and can’t spare a lot of pins for LEDs or switches, but you need a multi-LED display or a small keypad. Again shift registers can come to the rescue.

+ +

Let’s examine these handy devices.

+ +

Shift Registers

+ +

Shift Registers are sequential logic circuits that are used for the conversion, storage or transfer of binary data.

+ +

+ +

These devices are used to convert between serial and parallel data. They can be used in data communications circuits as well as memory and buffer circuits. Many complex electronic circuits, such as microprocessors and microcontrollers, use shift registers internally.

+ +

Types of Shift Registers

+ +

Shift registers deal with both serial and parallel data on both their inputs and outputs and they can convert between these formats.

+ +

There are four basic types of shift registers:

+ +

Serial In – Parallel Out

+ +

+ +

The Parallel In – Serial Out (PISO) shift register converts parallel data to serial data. It is used in communications and to convert multiple input ports to serial data.

+ +

Parallel In – Serial Out

+ +

+ +

The Parallel In – Serial Out (PISO) shift register converts parallel data to serial data. It is used in communications and to convert multiple input ports to serial data.

+ +

Parallel In – Parallel Out & Serial In – Serial Out

+ +

+ +

+ +

You might find these two strange. Why would you want a shift register that outputs data in the same format that it was input?

+ +

The answer is that this can be used as a buffer, to hold data for a specific number of clock cycles. The shift registers we are using today both employ similar buffers to hold data on their inputs and outputs so it doesn’t change while the register is being shifted through.

+ +

How Shift Registers work

+ +

Internally shift registers consist of a number of basic logic gates, many arranged as “flip flops(触发器)”.

+ +

If you aren’t familiar with a flip-flop it is a basic electronic circuit that can act to hold the value of data from its input. It is a fundamental building block and is used everywhere, including in many forms of memory circuitry.

+ +

+ +

A Serial In – Parallel Out, or SIPO, register uses a series of flip-flops, one for each bit on the parallel output. The illustrations here show a 4-bit device.

+ +

+ +

As the first bit of serial data is clocked in it is stored in the flip-flop and appears on its output.

+ +

+ +

The next bit of data pushes the original bit over to the next flip-flop.

+ +

+ +

This process continues as the serial data is clocked in. Note that the flip-flop only updates the input value when it is clocked.

+ +

+ +

Finally when all the data is clocked in the parallel output can be read. In most shift registers an extra buffer holds the parallel data and doesn’t change it until all the data is clocked in.

+ +

A PISO, or Parallel In – Serial Out shift register is constructed as follows

+ +

+ +

The “MUX” section of this diagram actually consists of a number of discrete logic gates, and they serve to feed the data into their associated flip-flop at the correct time.

+ +

This is important as a PISO shift register needs to clock each bit of the parallel data individually. This means that the data on the parallel input can’t change while it is being read, again most practical designs employ a buffer to hold the parallel data.

+ +

Cascading Shift Registers

+ +

A shift register is cataloged by the number of bits it handles, the ones shown in the previous illustrations were 4-bit registers and both of the shift registers that we will be using today are 8-bit devices.

+ +

If you need to increase the amount of parallel data you can handle with a shift register you can cascade it with another shift register. So two 8-bit shift registers can support 16-bits, add another one for 24-bits, etc.

+ +

You don’t need additional connections to the microcontroller to cascade shift registers, so it’s a great way to drive a lot of LEDs or read a lot of switches without using up a lot of ports.

+ +

74HC575 & 74HC165 Shift Registers

+ +

We will be using two very common and easily obtained shift registers today, the 74HC595 SIPO and 74HC165 PISO. Let’s take a closer look at these chips.

+ +

74HC595 – 8-bit Serial In – Parallel Out

+ +

The 74HC575 is an 8-stage serial shift register that also has an internal storage register. The storage register buffers the output data and can be clocked independently of the shift register. This prevents the data from changing while it is being loaded.

+ +

The 74HC595 has a “3-state” output. This means that the pins on the parallel data output can be at three different states.

+ +
    +
  • LOW
  • +
  • HIGH
  • +
  • OFF
  • +
+ +

The OFF state is a high-impedance state that effectively disconnects the output of the chip. This technique allows several 3-state chips to drive the same bus, with only one of them active at any given time.

+ +

The pinout of the 74HC575 in the DIP package is illustrated here:

+ +

+ +

The serial data is input on the DS pin (pin 14). You can use the Q7’ pin to cascade these devices to increase the number of parallel outputs you can control.

+ +

The Output Enable (pin 13) controls the 3-state bus, if it is LOW then the output bus is enabled.

+ +

74HC165 – 8-bit Parallel In – Serial Out

+ +

The 74HC165 is an 8-bit parallel-load shift register which has a serial output. It has complementary outputs, one of which can be tied to another 74HC165 to cascade them.

+ +

This device is used for parallel to serial data conversion and has the following pinout:

+ +

+ +

As with the 74HC595, this is a very common integrated circuit and you should have no trouble obtaining it at almost any electronics supplier.

+ +

Extra Output Ports with the 74HC595

+ +

We will begin our experiments with the 74HC595 SIPO (Serial In – Parallel Out) shift register.

+ +

The 74HC595 allows us to expand the number of digital I/O ports on our Arduino. In these experiments, we will be using it to drive some LEDs, which we will control using an Arduino.

+ +

Arduino & 74HC595 Hookup

+ +

Here is how we will be hooking up the 74HC595 to an Arduino and to eight LEDs.

+ +

+ +

Note the addition of a decoupling capacitor, across the power supply, this is a good idea when working with TTL chips like the 74HC595. I used a 100uf capacitor but any value from 10uf upwards will work just fine. Make sure that you observe the polarity of the capacitor.

+ +

On my breadboard, I replaced the eight dropping resistors with an 8×2 220-ohm resistor array. This is a convenient component to have when you need a lot of identical resistors. Of course, you may use discrete resistors if you don’t have an array.

+ +

There are a lot of wires here so double-check your wiring. You may test the LED-dropping resistor combos by wiring them first and then applying 5 volts to the resistor, if it is wired correctly you’ll light the LED. Repeat the test for all eight resistor-LED pairs. Do this before you wire up the 74HC595 and the Arduino.

+ +

Once it is all wired up you can move forward and write some code to make it all work.

+ +

Arduino shiftOut() Function

+ +

There are a couple of ways to “talk” to a shift register with an Arduino. One way is to use the SPI bus, which would allow you to make use of existing libraries to simplify writing code.

+ +

Another way is to use any standard I/O pins on the Arduino to create a clock and to exchange serial data. This is the method we will employ to work with the 74HC595 shift register.

+ +

Arduino provides a shiftOut() function to simplify moving data on a serial connection. It can take a byte value and output it in a serial format in sync with a clock pulse on another pin. You can choose to output the data in two directions.

+ +
    +
  • MSB First – The Most Significant Bit first. So the binary number 10110010 will output a bit at a time starting with “101”, or from left to right.
  • +
  • LSB First – The Least Significant Bit first. In this case, the binary number 10110010 will output a bit at a time starting with “010”, or from right to left.
  • +
+ +

We will make use of this function in our sketch.

+ +
+

参考: +shiftOut 函数用于一次传输一位数据(MSB/LSB First). 以 MSB 为例具体做法如下:

+ +
    +
  1. 传输字节的最高位到 dataPin
  2. +
  3. 将 clockPin 拉高, 再降低
  4. +
+
+ +

Arduino & 74HC595 Sketch

+ +

Our sketch is pretty simple. The shiftOut function takes care of sending our data to the shift register and of creating a clock signal.

+ +
/*
+  74HC595 Shift Register Demonstration 1
+  74hc595-demo.ino
+  Count in Binary and display on 8 LEDs
+
+  Modified from "Hello World" example by Carlyn Maw,Tom Igoe and David A. Mellis
+
+  DroneBot Workshop 2020
+  https://dronebotworkshop.com
+*/
+
+// Define Connections to 74HC595
+
+// ST_CP pin 12
+const int latchPin = 10;
+// SH_CP pin 11
+const int clockPin = 11;
+// DS pin 14
+const int dataPin = 12;
+
+void setup ()
+{
+  // Setup pins as Outputs
+  pinMode(latchPin, OUTPUT);
+  pinMode(clockPin, OUTPUT);
+  pinMode(dataPin, OUTPUT);
+}
+
+void loop() {
+  // Count from 0 to 255 and display in binary
+
+  for (int numberToDisplay = 0; numberToDisplay < 256; numberToDisplay++) {
+
+    // ST_CP LOW to keep LEDs from changing while reading serial data
+    digitalWrite(latchPin, LOW);
+
+    // Shift out the bits
+    shiftOut(dataPin, clockPin, MSBFIRST, numberToDisplay);
+
+    // ST_CP HIGH change LEDs
+    digitalWrite(latchPin, HIGH);
+
+    delay(500);
+  }
+}
+
+ +

This sketch is just an adaptation of one that Arduino provides in their excellent lesson on using the 74HC595 with the Arduino. It uses the eight LEDs as the display for a binary counter.

+ +

We begin by assigning variable names to the pins connected to the 74HC595. All of these pins are then set up as outputs. We then move on to the Loop.

+ +

We use a for-next loop to count from 0 to 255, incrementing by one. On each increment, we write the counter value out to the shift register. The latch pin is used to hold the data until we are ready so that the display doesn’t flicker as the shift register is being loaded.

+ +

After a half-second delay, the next number is loaded.

+ +

+ +

The result is that the LEDs display a binary count from 0 to 255.

+ +

You can experiment with the code and manipulate some values and observe the effect on the LEDs. Try changing the MSBFIRST parameter in the shiftOut statement to LSBFIRST and see what happens.

+ +

It’s a simple way to understand basic shift register operation.

+ +

Driving 7-Segment Displays

+ +

Another use for the 74HC575 is to drive a 7-segment LED display. You can use it to reduce the number of connections to one display, or you can cascade multiple 74HC595s to drive several displays.

+ +

7-Segment LED Displays

+ +

A typical 7-segment LED display layout is shown here:

+ +

+ +

Note that there are actually eight LED elements in a “7-Segment” display, the eighth LED is used as a decimal point. In some displays, this may be substituted with a colon.

+ +

LED displays are available in two configurations:

+ +

Common Anode – All the LEDs are connected with a common Anode (positive) connection. +Common Cathode – All the LEDs are connected with a common Cathode (negative) connection. +Both display types use the same pinout, so it is very important to know what type you have. A good way to tell (other than referencing the display part number) is to use a multimeter on the “diode test” function. When connected with the proper polarity it can be used to light the LED elements.

+ +

The Common Cathode display is more common and is the type we will be using for our experiment.

+ +

74HC595 7-Segment Display Hookup

+ +

As the Common Cathode 7-segment LED display is really just eight LEDs connected to a common cathode( (negative) terminal it is no different than the eight LEDs we used in our first experiment. So we can use the exact same circuit to hook it up.

+ +

+ +

Use the chart in the hookup diagram to connect the display pins to the dropping resistors. The COM pin (the Common Cathode) is connected to the Arduino’s Ground. Note that the display will have two COM pins, you only need to connect one.

+ +

Once you have that all hooked up you can test it by running the previous sketch, which should test all the LED segments including the decimal point.

+ +

But to actually display something coherent we will need a different sketch.

+ +

74HC595 7-Segment Display Sketch

+ +

Here is the sketch we will use to test out our 7-segment display.

+ +
/*
+  74HC595 Shift Register with 7-segment LED display
+  74hc595-7segdisplay.ino
+  Count in hex from 0-F and display on 7-segment Common Cathode LED display
+
+  DroneBot Workshop 2020
+  https://dronebotworkshop.com
+*/
+
+// Define Connections to 74HC595
+
+// ST_CP pin 12
+const int latchPin = 10;
+// SH_CP pin 11
+const int clockPin = 11;
+// DS pin 14
+const int dataPin = 12;
+
+// Patterns for characters 0,1,2,3,4,5,6,7,8,9,A,b,C,d,E,F
+int datArray[16] = {B11111100, B01100000, B11011010, B11110010, B01100110, B10110110, B10111110, B11100000, B11111110, B11110110, B11101110, B00111110, B10011100, B01111010, B10011110, B10001110};
+
+void setup ()
+{
+  // Setup pins as Outputs
+  pinMode(latchPin, OUTPUT);
+  pinMode(clockPin, OUTPUT);
+  pinMode(dataPin, OUTPUT);
+}
+void loop()
+{
+  // Count from 0 to 15
+  for (int num = 0; num < 16; num++)
+  {
+    // ST_CP LOW to keep LEDs from changing while reading serial data
+    digitalWrite(latchPin, LOW);
+
+    // Shift out the bits
+    shiftOut(dataPin, clockPin, MSBFIRST, datArray[num]);
+
+    // ST_CP HIGH change LEDs
+    digitalWrite(latchPin, HIGH);
+
+    delay(1000);
+  }
+}
+
+ +

This sketch has many similarities to the previous one, which isn’t surprising when you consider that it does pretty well the same thing.

+ +

We start again by defining our connections to the 74HC595.

+ +

Then we create an array with 16 elements, each one representing the pattern for a character to display on the 7-segment LED.

+ +

The elements are written in binary and it makes it simple to understand how they work. Within the binary byte, each bit represents one of the LED segments. From MSB to LSB (left to right) they represent the following segments in the display:

+ +
a – b – c – d – e – f – g – DP
+
+ +

When the bit is set to “1” the LED segement is on, a “0” means it is off.

+ +

The array is ordered so element 0 is the character for “0”. Element 1 is the character for “1” etc. It is in hexadecimal so element 15 is “F”.

+ +

Look at the array elements and you’ll see the pattern.

+ +

Once again in Setup, we set our connections as outputs and then move onto the Loop.

+ +

Again we use a counter, only this time ist is between 0 and 15. We will display these values as they are counted on the LED display in Hexadecimal format.

+ +

We step through the array one element at a time, using shiftOut to send the element data to the shift register.

+ +

+ +

Load up the code and observe the display. If everything is connected correctly you’ll see it count from 0 to F and then repeat.

+ +

Extra Input Ports with the 74HC165

+ +

Now that we have seen how to add output ports with a shift register it’s time to do the opposite and add some inputs. For that job, we will use the 74HC165.

+ +

We will use the 74HC165 shift register along with eight momentary-contact pushbutton switches. The shift register will take the 8 inputs from the switch and send them to the Arduino as serial data.

+ +
+

74HC165 的大概工作原理如下: +TODO:

+
+ +

Arduino shiftIn() Function

+ +

Once again Arduino has a dedicated function for receiving serial data.

+ +

The shiftIn() function shifts serial data in one byte at a time. It can be set to take the MSB or LSB first. It is often used with a shift register like the 74HC165 or the CD4021BE.

+ +

As with its cousin the shiftOut function the shiftIn function also supplies a clock signal to synchronize the data transmission.

+ +

Arduino & 74HC165 Hookup

+ +

The inputs of the 74HC165 need to be pulled down LOW to prevent false readings, so in addition to our eight pushbutton switches we will also need eight pulldown resistors. I used 10k resistors, but any value from 4.7k to 27k will work fine.

+ +

+ +

Once again I used a 100uf decoupling capacitor, make sure you observe the polarity when connecting this.

+ +

Once you have it all hooked up we can focus on the sketch we will be using to make this work.

+ +

Arduino & 74HC165 Sketch

+ +

Our sketch is very simple, as all it does is read the status of the pushbuttons and display the results on the serial monitor. But that’s really all you need to do to understand how to get data from your pushbuttons and the 74HC165.

+ +
/*
+  74HC165 Shift Register Demonstration 1
+  74hc165-demo.ino
+  Read from 8 switches and display values on serial monitor
+
+  DroneBot Workshop 2020
+  https://dronebotworkshop.com
+*/
+
+// Define Connections to 74HC165
+
+// PL pin 1
+int load = 7;
+// CE pin 15
+int clockEnablePin = 4;
+// Q7 pin 7
+int dataIn = 5;
+// CP pin 2
+int clockIn = 6;
+
+void setup()
+{
+
+  // Setup Serial Monitor
+  Serial.begin(9600);
+
+  // Setup 74HC165 connections
+  pinMode(load, OUTPUT);
+  pinMode(clockEnablePin, OUTPUT);
+  pinMode(clockIn, OUTPUT);
+  pinMode(dataIn, INPUT);
+}
+
+void loop()
+{
+
+  // Write pulse to load pin
+  digitalWrite(load, LOW);
+  delayMicroseconds(5);
+  digitalWrite(load, HIGH);
+  delayMicroseconds(5);
+
+  // Get data from 74HC165
+  digitalWrite(clockIn, HIGH);
+  digitalWrite(clockEnablePin, LOW);
+  byte incoming = shiftIn(dataIn, clockIn, LSBFIRST);
+  digitalWrite(clockEnablePin, HIGH);
+
+  // Print to serial monitor
+  Serial.print("Pin States:\r\n");
+  Serial.println(incoming, BIN);
+  delay(200);
+}
+
+ +

The sketch starts out like all of our previous sketches, defining the four connections to the IC.

+ +

In the Setup, we initialize the serial monitor and then set the connections up as required.

+ +

In the Loop, we first write a pulse to the load pin, which will have it load the data from its parallel input into a buffer to be worked on.

+ +
+

循环中, 给 PL 一个 “低->高” 脉冲, 可以让 74HC165 将当前 D0 ~ D7 引脚上的位数据保存至缓存区, 后面可以使用 shiftIn 讲这些数据读取出来. 接下来设置启用时钟(CL 为高电平, CE 为低电平), 然后通过 COMPLEMENTARY OUTPUT 引脚(pin 7)串行的把数据读取出来. 读完之后将 CE 重新置为高电平禁用时钟. +TODO: pin 7, 9, 10 有啥区别?

+
+ +

Next, we set up the 74HC165 to prepare to send data and then use the shiftIn function to get that data, LSB (Least Significant Bit) first. We finish by taking the clock pin HIGH, signifying we are done.

+ +

Finally, we print the result to the serial monitor.

+ +

Load the sketch, open your serial monitor and observe the output. Right away you’ll notice something.

+ +

+ +

The data is all held HIGH on the output, the opposite of what is wired on the board. Pressing a pushbutton will cause it to read LOW, even though that is the opposite of what is really happening.

+ +

This is because we are using the complementary output from the 74HC165. Our data is inverted.

+ +

I’ll show you in a later sketch how to turn it back the right way around. Keep on reading!

+ +

The example we just used has many practical applications, one of the obvious ones is as a keypad (although there are better ways to make a large keypad). For a project that requires a lot of switches, it is a useful design technique.

+ +

One great application of this circuit is to use it with DIP switches or jumpers, ones that are only occasionally set. You can use the 74NC165 to reduce the number of connections required to read an 8-position DIP switch, and just read it in the Setup routine so it is only read when the device is powered on or reset.

+ +

Using the 74HC595 and 74HC165 Together

+ +

Of course, it would be a shame to have wired up all of those LEDs and switches without going the extra step to connect them together! So let’s do exactly that.

+ +

74HC595 and 74HC165 Hookup

+ +

If you built each of the demonstrations on its own solderless breadboard as I did then hooking both of them together is very simple.

+ +

+ +

Disconnect the Arduino from its breadboard on one of the demonstrations, it doesn’t really matter which one. Leave the connections on the breadboard so that you can reconnect them to the other Arduino. You can connect the 5-volt and Ground connections to the other breadboards power rails.

+ +

When you are done try running the previous sketches on the Arduino, everything should still work. If something doesn’t work then check your wiring, something might have become disconnected when you joined the projects – there are a lot of wires here!

+ +

Once you have everything tested it’s time to look at a sketch to use both the 74HC165 and 74HC595 together.

+ +

74HC595 and 74HC165 Sketch 1 – Boring!

+ +

As our demo is essentially two demos fused together our sketch is pretty well the same thing. You’ll see a lot of similarities between this sketch and the previous ones, and it’s no accident – some of it is literally cut and paste!

+ +

The purpose of the sketch is to simply use the LEDs to display the status of the pushbuttons. Really boring, and a complete waste of a microcontroller and some shift registers, but as a demonstration, it works well. I promise we’ll move on to something more exciting after this!

+ +
/*
+  74HC595 & 74HC165 Shift Register Demonstration
+  74hc595-to-74ch165.ino
+  Input for 8 pushbuttons using 74HC165
+  Output to 0 LEDs using 74HC595
+
+  DroneBot Workshop 2020
+  https://dronebotworkshop.com
+*/
+
+// Define Connections to 74HC165
+
+// PL pin 1
+int load = 7;
+// CE pin 15
+int clockEnablePin = 4;
+// Q7 pin 7
+int dataIn = 5;
+// CP pin 2
+int clockIn = 6;
+
+// Define Connections to 74HC595
+
+// ST_CP pin 12
+const int latchPin = 10;
+// SH_CP pin 11
+const int clockPin = 11;
+// DS pin 14
+const int dataPin = 12;
+
+void setup () {
+
+  // Setup Serial Monitor
+  Serial.begin(9600);
+
+  // 74HC165 pins
+  pinMode(load, OUTPUT);
+  pinMode(clockEnablePin, OUTPUT);
+  pinMode(clockIn, OUTPUT);
+  pinMode(dataIn, INPUT);
+
+  // 74HC595 pins
+  pinMode(latchPin, OUTPUT);
+  pinMode(clockPin, OUTPUT);
+  pinMode(dataPin, OUTPUT);
+
+}
+
+
+void loop() {
+
+  // Read Switches
+
+  // Write pulse to load pin
+  digitalWrite(load, LOW);
+  delayMicroseconds(5);
+  digitalWrite(load, HIGH);
+  delayMicroseconds(5);
+
+  // Get data from 74HC165
+  digitalWrite(clockIn, HIGH);
+  digitalWrite(clockEnablePin, LOW);
+  byte incoming = shiftIn(dataIn, clockIn, LSBFIRST);
+  digitalWrite(clockEnablePin, HIGH);
+
+  // Print to serial monitor
+  Serial.print("Pin States:\r\n");
+  Serial.println(incoming, BIN);
+
+
+  // Write to LEDs
+
+  // ST_CP LOW to keep LEDs from changing while reading serial data
+  digitalWrite(latchPin, LOW);
+
+  // Shift out the bits
+  shiftOut(dataPin, clockPin, LSBFIRST, ~incoming);
+
+  // ST_CP HIGH change LEDs
+  digitalWrite(latchPin, HIGH);
+
+  delay(500);
+
+}
+
+ +

We stare by once again defining pin connections to both integrated circuits. And in the Setup, we initialize the serial monitor and set the connections up as required.

+ +

The Loop starts with the same routine we used earlier to read the pushbutton values from the 74HC165. Again we insert the data into an 8-bit byte called “incoming” and display its value on the serial monitor.

+ +

Next, we use the same code we used earlier to write the data to the 74HC595. But we make one change to the date we send to the shift register.

+ +

Remember, our data from the switch is inverted. If we send it to the 74HC595 it will work, but the LEDs will all be on, except where we have pressed a pushbutton.

+ +

To invert the date we use the “~” symbol in front of the “incoming” variable when we use it in the shiftOut function. The tilde (squiggly) symbol inverts the binary data, turning every zero into a one and vice versa. Exactly what we need to do.

+ +

You’ll also notice that one thing we do differently from the earlier 74HC595 sketch is we send the data LSB first. This matches how we receive it from the pushbuttons. Sending it MSB first would work, but the LED display would be reversed.

+ +

Load it up and try it out.

+ +

+ +

Thrilling, isn’t it?

+ +

OK, it really isn’t, but it does show how you can take parallel data (the switch inputs), convert it to serial with a shift register, send it to an Arduino, send it back out to a second shift register and convert it back into parallel again (the LED outputs). And that’s sort of thrilling.

+ +

If you aren’t completely thrilled then don’t worry, we can do other things now that we have eight switches and eight LEDs.

+ +

74HC595 and 74HC165 Sketch 2 – Exciting!

+ +

To add some excitement to our demo let’s use the eight switches to select LED light flashing patterns. As we have eight switches we can select eight patterns.

+ +

Here is how we will do it:

+ +
/*
+  74HC595 & 74HC165 Shift Register Demonstration 2
+  74hc595-to-74ch165-pattern.ino
+  Input from 8 pushbuttons using 74HC165
+  Output to 8 LEDs using 74HC595
+
+  Select LED pattern using pushbuttons
+
+  DroneBot Workshop 2020
+  https://dronebotworkshop.com
+*/
+// Define Connections to 74HC165
+
+// PL pin 1
+int load = 7;
+// CE pin 15
+int clockEnablePin = 4;
+// Q7 pin 7
+int dataIn = 5;
+ // CP pin 2
+int clockIn = 6;
+
+// Define Connections to 74HC595
+
+// ST_CP pin 12
+const int latchPin = 10;
+// SH_CP pin 11
+const int clockPin = 11;
+// DS pin 14
+const int dataPin = 12;
+
+// Define data array
+int datArray[8];
+
+void setup () {
+
+// Setup Serial Monitor
+Serial.begin(9600);
+
+// 74HC165 pins
+pinMode(load, OUTPUT);
+pinMode(clockEnablePin, OUTPUT);
+pinMode(clockIn, OUTPUT);
+pinMode(dataIn, INPUT);
+
+// 74HC595 pins
+pinMode(latchPin,OUTPUT);
+pinMode(clockPin,OUTPUT);
+pinMode(dataPin,OUTPUT);
+
+}
+
+
+void loop() {
+
+// Read Switches
+
+// Write pulse to load pin
+digitalWrite(load,LOW);
+delayMicroseconds(5);
+digitalWrite(load,HIGH);
+delayMicroseconds(5);
+
+// Get data from 74HC165
+digitalWrite(clockIn,HIGH);
+digitalWrite(clockEnablePin,LOW);
+byte incoming = shiftIn(dataIn, clockIn, LSBFIRST);
+digitalWrite(clockEnablePin,HIGH);
+
+// Print to serial monitor
+Serial.print("Pin States:\r\n");
+Serial.println(incoming, BIN);
+
+// Setup array for LED pattern
+
+switch (incoming) {
+
+  case B11111110:
+
+    datArray[0] = B11111111;
+    datArray[1] = B01111110;
+    datArray[2] = B10111101;
+    datArray[3] = B11011011;
+    datArray[4] = B11100111;
+    datArray[5] = B11011011;
+    datArray[6] = B10111101;
+    datArray[7] = B01111110;
+
+    break;
+
+  case B11111101:
+
+    datArray[0] = B00000001;
+    datArray[1] = B00000010;
+    datArray[2] = B00000100;
+    datArray[3] = B00001000;
+    datArray[4] = B00010000;
+    datArray[5] = B00100000;
+    datArray[6] = B01000000;
+    datArray[7] = B10000000;
+
+    break;
+
+  case B11111011:
+
+    datArray[0] = B10000001;
+    datArray[1] = B01000010;
+    datArray[2] = B00100100;
+    datArray[3] = B00011000;
+    datArray[4] = B00000000;
+    datArray[5] = B00100100;
+    datArray[6] = B01000010;
+    datArray[7] = B10000001;
+
+    break;
+
+
+  case B11110111:
+
+    datArray[0] = B10101010;
+    datArray[1] = B01010101;
+    datArray[2] = B10101010;
+    datArray[3] = B01010101;
+    datArray[4] = B10101010;
+    datArray[5] = B01010101;
+    datArray[6] = B10101010;
+    datArray[7] = B01010101;
+
+    break;
+
+
+  case B11101111:
+
+    datArray[0] = B10000000;
+    datArray[1] = B00000001;
+    datArray[2] = B01000000;
+    datArray[3] = B00000010;
+    datArray[4] = B00100000;
+    datArray[5] = B00000100;
+    datArray[6] = B00010000;
+    datArray[7] = B00001000;
+
+    break;
+
+  case B11011111:
+
+    datArray[0] = B11000000;
+    datArray[1] = B01100000;
+    datArray[2] = B00110000;
+    datArray[3] = B00011000;
+    datArray[4] = B00001100;
+    datArray[5] = B00000110;
+    datArray[6] = B00000011;
+    datArray[7] = B10000001;
+
+    break;
+
+ case B10111111:
+
+    datArray[0] = B11100000;
+    datArray[1] = B01110000;
+    datArray[2] = B00111000;
+    datArray[3] = B00011100;
+    datArray[4] = B00001110;
+    datArray[5] = B00000111;
+    datArray[6] = B10000011;
+    datArray[7] = B11000001;
+
+    break;
+
+ case B01111111:
+
+    datArray[0] = B10001000;
+    datArray[1] = B01000100;
+    datArray[2] = B00100010;
+    datArray[3] = B00010001;
+    datArray[4] = B10001000;
+    datArray[5] = B01000100;
+    datArray[6] = B00100010;
+    datArray[7] = B00010001;
+
+    break;
+
+  default:
+
+    break;
+
+  }
+
+// Write to LEDs
+
+// Count from 0 to 7
+  for(int num = 0; num < 8; num++)
+  {
+    // ST_CP LOW to keep LEDs from changing while reading serial data
+    digitalWrite(latchPin, LOW);
+
+    // Shift out the bits
+    shiftOut(dataPin,clockPin,MSBFIRST,datArray[num]);
+
+    // ST_CP HIGH change LEDs
+    digitalWrite(latchPin, HIGH);
+
+    delay(200);
+  }
+
+}
+
+ +

As you can see I’ve once again borrowed coded from all of the previous demos. In fact, there is really only one thing different about this sketch, and that’s the switch-case statement that allows you to select the LED patterns.

+ +

We use the ”incoming” byte, which holds the switch values, as the switch for the statement. Then we have eight cases, one for each switch press. You could probably add more if you wanted to allow for pressing two buttons simultaneously, but eight was enough for me!

+ +

In each case section, we populate the datArray array with LED patterns, written in binary so they are easy to see. In each byte, a “1” represents an LED that is illuminated, whereas “0” indicates the LED is off.

+ +

I used eight elements in the array to make it easier, but you can increase this to any number you like. Just change the number in the array definition and in the for-next loop that cycles through the array elements.

+ +

I set the delay between pattern changes to 200ms, but you can change that. Even better, try putting the delay as a variable in each case evaluation, so you can make the patterns run at different speeds.

+ +

+ +

The sketch runs as advertised, and it’s actually a lot of fun to watch.

+ +

You could improve on the sketch by cascading the 74HC595 to add more LEDs. You could also make the speed variable by adding a potentiometer to one of the analog inputs and using it to set the delay time. And the enable input on the 74HC595 can be driven with PWM to change LED intensity, which you could control with a second potentiometer.

+ +

You could even add some MOSFETs to drive larger LEDs and make your own marquis!

+ +

A simple demo with lots of potential.

+ +

Conclusion

+ +

Shift registers may be elementary building blocks that might seem out of place aside microcontrollers and other more capable chips, but they still can perform important functions in a modern design. They can be very useful if you need to add extra inputs or outputs to your project, they are inexpensive and easy to use.

+ +

Nothing shifty about that!

+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2022/08/31/arduino-schematic.html b/2022/08/31/arduino-schematic.html new file mode 100644 index 0000000..a39aa64 --- /dev/null +++ b/2022/08/31/arduino-schematic.html @@ -0,0 +1,1314 @@ + + + +如何阅读 Arduino 原理图 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

如何阅读 Arduino 原理图

  + +
+
+ + +
+

英文原文: https://learn.circuit.rocks/the-basic-arduino-schematic-diagram

+
+ +

+ +

Get deeper into the Arduino craft by looking into a reference design. In here I will get into details with the basic arduino schematic diagram using one of their more popular development board, the Arduino UNO.

+ + + +

So what is an Arduino? It can basically mean two things:

+ +

One, it is an open-source hardware and software company that creates circuit boards that makes microcontrollers easier to use. Open-source means its source code and hardware design is freely available for anyone to modify. With the code and design free, public collaboration can develop the product way faster than any proprietary product. The only downside of giving the public access is that it gives birth to competition. It allows clones. This is the also the reason why we can only do a diagram analysis of Arduino, and not a Raspberry Pi.

+ +

Two and the more popular use of the term is that the board itself is called an Arduino.

+ +

There are a lot of tutorials for Arduino in the internet. Nearly all of them explains how to make it work but, how does it work? A good understanding of the hardware design will help you learn how to incorporate them on your machines, small-scale to large.

+ +

Here we have the schematic diagram of the latest revision of Arduino UNO. Don’t worry if you can’t see it much right now. We’ll chop it up after and analyze every section according to their function.

+ +

+ +

Components

+ +

+ +

Here we can visibly identify each component on board, besides the obvious Atmega328P. Depending on the IC package, these components can appear differently. If you want to learn more about IC packages. Visit this SparkFun webpage.

+ +

We will divide the schematic diagram into four major sections: Power Supply, Main Microcontroller, USB bridge and lastly the Input and Output.

+ +

+ +

POWER SUPPLY

+ +

+ +

This section of the schematic diagram powers our Arduinos. For this, we can use either DC power supply or USB connection as a source. To trace how the circuit works, let us start with the 5V linear voltage regulator(5V 线性稳压器) NCP1117ST50T3G. This regulator has a pretty straightforward function. It takes voltage input up to 20V and converts it to 5V. The Vin of this regulator is connected to the DC power supply via the M7 diode. This diode provides reverse polarity protection, which means current can only flow from the power supply to the regulator and not the other way around.

+ +

There are two kinds of capacitors used in this circuit. One, the polarized capacitors PC1 and PC2. They are used to filter supply noise. Two, the 100nF capacitor on the far right side acting as a decoupling capacitor. Decoupling capacitors “disconnects” circuit elements to reduce its effect on the rest of the circuit. Also, notice the +5V terminals circled in blue? You can see them around the schematic diagram. That is basically a connection. Designers often use these terminals so that they can fit their design in the least amount of pages. Labels like Vin and GND also works the same.

+ +

+ +

Choosing the Power Source

+ +

If you use the USB source, power will enter through the USBVCC section into a P-channel MOSFET FDN340P. Explaining briefly, MOSFETS are transistors(晶体管) that are normally closed (connected) until a certain amount of voltage triggers the gate. Consequently, triggering the MOSFET opens the transistor (disconnected). “触发晶体管会导致其断开”. Using this characteristic, a comparator is connected to the gate terminal of the MOSFET to function as a switch mechanism. If you use the USB source, it will just power up the 3.3V regulator since the MOSFET is normally connected. If you plug a dc supply, the LMV358 op-amp(运算放大器) will produce a high output triggering the gate of the MOSFET, disconnecting the USB source from the 3.3V regulator. So if both are plugged, the board will use the DC power supply.

+ +
+

注意电源选择这块只使用了 LMV358 的 1/2/3 引脚. 引脚 4 (GND) 和引脚 8 (VCC) 分别接入电路里面的地线和 +5V(虽然 +5V 是经过 LMV358 比较完才知道电源是来自 USB 口还是来自 DC 口). +TODO: LVM358 的 5/6/7 引脚是干啥的? 貌似和 USB 有关系

+
+ +

+

+ +

The 5V terminal here indicates a connection to the circuit powered by the DC power supply earlier. This means that the 3.3V regulator is powered by whichever powers the circuit.

+ +

+ +

Lastly, the LED indicator circuit. Put simply, it lights the green LED up when it detects power from the 5V terminal.

+ +

MICROCONTROLLER

+ +

+ +

This section is the main microcontroller circuit of the Arduino UNO. It has the ATmega328P microcontroller. We won’t go into details with the microcontroller’s specs. The only thing you need to know is that it is an 8-bit device which means it can handle 8 data lines simultaneously.

+ +

+ +

These three are the black headers you see on board. As you can see, the TX and RX from the ATmega16U2 are connected into pins 0 and 1. The IOL and IOH headers are for pins 0 to 13 in addition to the I2C pins, GND, and AREF.

+ +

+ +

There’s a CSTCE16M0V53-R0 16MHz ceramic(陶瓷) resonator(谐振器(振荡器 +) of the microcontroller. This circuit serves as the clock.

+ +

The capacitors here has the same function with the decoupling capacitors in the previous section.

+ +

+ +

The ICSP (In-Circuit Serial Programming, 在线串行编程) Header is used to program the ATmega328P using an external programmer (e.g. Raspberry Pi). Usually, we only use this for remote programming or for flashing of the microcontroller for the first time. We don’t use this way of programming often since we can already do it via USB, which we will discuss in the next section.

+ +

USB

+ +

+ +

This section is the USB to UART bridge. Since the main microcontroller, ATmega328, does not have a USB transceiver, we use the ATmega16U2 microcontroller as a bridge to translate the USB signals from your computer to UART, the communication protocol the ATmega328 uses.

+ +

This is basically the same with the main microcontroller section. This MCU has capacitors, an ICSP header and a crystal as well.

+ +

+ +

These RN3 resistors are termination resistors. They are used so that the total impedance of the line matches with the USB specification. It slows the speed of the USB pulses lessening EMI interference. On the other hand, Z1 and Z2 are varistors. They are used for ESD (Electrostatic Discharges) protection.

+ +
+

这些 RN3 电阻器是终端电阻器。使用它们是为了使线路的总阻抗符合 USB 规范。它减慢了 USB 脉冲的速度,从而减少了 EMI 干扰。另一方面,Z1 和 Z2 是压敏电阻。它们用于 ESD(静电放电)保护。

+
+ +

+ +

When the ATmega 16U2 transmits or receives a signal from the ATmega 328P, yellow LEDs light up. We can also see here the two lines where the two microcontrollers communicate.

+ +

I/O

+ +

+ +

Image courtesy of MarcusJenkins

+ +

Quite refreshing to see the board again right? By now, you should be able to appreciate how these are connected to the several electronic components in our schematic diagram. We don’t need to explain them anymore. This is an article for the basic arduino schematic diagram after all.

+ +

参考资料

+ + + +

TODO

+ +
    +
  • 去耦电容/降噪电容
  • +
  • LMV358 5/6/7 脚在电路图中的原理是什么?
  • +
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2022/08/31/avr-gcc-tutorial.html b/2022/08/31/avr-gcc-tutorial.html new file mode 100644 index 0000000..2aacb92 --- /dev/null +++ b/2022/08/31/avr-gcc-tutorial.html @@ -0,0 +1,1192 @@ + + + +AVR GCC 教程 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

AVR GCC 教程

  + +
+
+ + +
+

英文原文: https://makenotes.de/2018/12/avr-gcc-avr-libc-tutorial-for-linux-and-macos/

+
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2022/10/12/homebrew.html b/2022/10/12/homebrew.html new file mode 100644 index 0000000..26911b5 --- /dev/null +++ b/2022/10/12/homebrew.html @@ -0,0 +1,1258 @@ + + + +Mac 下 Homebrew 相关使用 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Mac 下 Homebrew 相关使用

  + +
+
+ + +

Homebrew 操作介绍

+ +

网上的更新国内源大多不完整,导致 brew update 失败 +先更新下 brew +有时 brew 版本太旧也会有问题

+ + + +

先安装 Homebrew:

+ +
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
+
+ +

再更新国内源

+ +
#更新Homebrew
+cd "$(brew --repo)"
+git remote set-url origin https://mirrors.ustc.edu.cn/brew.git
+
+#更新 Homebrew-core
+cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core"
+git remote set-url origin https://mirrors.ustc.edu.cn/homebrew-core.git
+
+#更新 Homebrew-cask(最重要的一步,很多更新完国内源依然卡就是没更新这个)
+cd "$(brew --repo)"/Library/Taps/homebrew/homebrew-cask
+git remote set-url origin https://mirrors.ustc.edu.cn/homebrew-cask.git
+
+ +

最重要, 更新 HOMEBREW_BOTTLE_DOMAIN

+ +
# 使用 zsh 的用户
+echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles/' >> ~/.zshrc
+source ~/.zshrc
+
+# 使用 bash 的用户
+echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles/' >> ~/.bash_profile
+source ~/.bash_profile
+
+ +

更新库

+ +
brew update-reset
+
+ +

关于软件升级

+ +

来自 StackExcange 的一些资料:

+ +

Homebrew manages all updating/upgrading by itself. Run brew update && brew upgrade every once in a while (and you can do it after upgrading macOS). brew update will update the list of available formulae, and brew upgrade will upgrade any outdated packages.

+ +

If you like, everything can be scripted as well:

+ +

Run the macOS installer: startosinstall +Run all macOS updates for Xcode, etc..: softwareupdate -ai +Update homebrew itself and the package lists: brew update +Upgrade all software installed with homebrew: brew upgrade +Upgrade all casks installed with homebrew: brew upgrade --cask +Remove old versions of installed software: brew cleanup

+ +

You don’t need to remove/uninstall anything before upgrading macOS. Just download the macOS installer from the App Store (or from System Preferences), and follow the instructions to install the new OS like normal.

+ +
+

参考资料:

+ +
    +
  1. https://cloud.tencent.com/developer/article/1817647
  2. +
  3. macOS 平台 Homebrew 更新 brew update 卡死,完美解决
  4. +
+
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2022/10/12/letsencrypt.html b/2022/10/12/letsencrypt.html new file mode 100644 index 0000000..543738f --- /dev/null +++ b/2022/10/12/letsencrypt.html @@ -0,0 +1,1222 @@ + + + +使用 Let's Encrypt 为网站添加 HTTPS 支持 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

使用 Let's Encrypt 为网站添加 HTTPS 支持

  + +
+
+ + +
+

WIP

+
+ +

Let’s Encrypt 介绍

+ + + +

完全手动版

+ +
certbot certonly --config-dir=config --work-dir=work --logs-dir=log -d "\*.iuwei.fun" --manual --preferred-challenges dns-01 --server https://acme-v02.api.letsencrypt.org/directory
+
+ +

自动版

+ +

Aliyun DNS 插件安装

+ +

参考链接: https://github.com/justjavac/certbot-dns-aliyun

+ +

Aliyun 自动添加 DNS 相关的 API

+ +

新签证书

+ +
certbot certonly --config-dir=config --work-dir=work --logs-dir=log -d "\*.iuwei.fun" -d "iuwei.fun" --manual --preferred-challenges dns-01 --manual-auth-hook "alidns" --manual-cleanup-hook "alidns clean" --server https://acme-v02.api.letsencrypt.org/directory
+
+ +

更新证书(可以用 crontab 跑)

+ +
certbot renew --config-dir=config --work-dir=work --logs-dir=log --manual --preferred-challenges dns-01 --manual-auth-hook "alidns" --manual-cleanup-hook "alidns clean" --deploy-hook "nginx -s reload"
+
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2022/10/12/virtualenv.html b/2022/10/12/virtualenv.html new file mode 100644 index 0000000..fa07a6e --- /dev/null +++ b/2022/10/12/virtualenv.html @@ -0,0 +1,1251 @@ + + + +Python 虚拟环境管理 virtualenvwrapper 相关使用 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Python 虚拟环境管理 virtualenvwrapper 相关使用

  + +
+
+ + +
+

https://stackoverflow.com/questions/49470367/install-virtualenv-and-virtualenvwrapper-on-macos +Virtualenv

+
+ +

To install virtualenv and virtualenvwrapper for repetitive use you need a correctly configured Python (this example uses Python 3.x but process is identical for Python 2.x).

+ +

Although you can get python installer from Python website I strongly advice against it. The most convenient and future-proof method to install Python on MacOS is brew.

+ + + +

Main difference between installer from Python website and brew is that installer puts python packages to:

+ +

/Library/Frameworks/Python.framework/Versions/3.x +Brew on the other hand installs Python, Pip & Setuptools and puts everything to:

+ +

/usr/local/bin/python3.x/site-packages +And though it may not make any difference to you now – it will later on.

+ +

Configuration steps +Install brew +Check out brew installation page or simply run this in your terminal:

+ +

/usr/bin/ruby -e “$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)” +Install Python +To install python with brew run:

+ +

brew install python3 +Now your system needs to know where to look for freshly installed Python packages. Add this line to youre ~/.zshrc (or ~/.bash_profile if you’re using bash):

+ +

export PATH=/usr/local/share/python:$PATH +Restart your terminal. To make sure you’ve done everything correctly run which python3 and in return you should receive /usr/local/bin/python.

+ +

Install virtualenv & virtualenvwrapper +Now it’s time to install virtualenv and virtualenvwrapper to be able to use workon command and switch between virtual environments. This is done using pip:

+ +

pip3 install virtualenv virtualenvwrapper +Set up virtualenv variables +Define a default path for your virtual environments. For example you can create a hidden directory inside ~ and called it .virtualenvs with mkdir ~/.virtualenvs. Add virtualenv variables to .zshrc (or .bash_profile).

+ +

Final version of your .zshrc (or .bash_profile) should contain this information to work properly with installed packages:

+ +

Setting PATH for Python 3 installed by brew

+ +

export PATH=/usr/local/share/python:$PATH

+ +

Configuration for virtualenv

+ +

export WORKON_HOME=$HOME/.virtualenvs +export VIRTUALENVWRAPPER_PYTHON=/usr/local/bin/python3 +export VIRTUALENVWRAPPER_VIRTUALENV=/usr/local/bin/virtualenv +source /usr/local/bin/virtualenvwrapper.sh +Restart your terminal. You should be able to use mkvirtualenv and workon commands including autocompletion.

+ +

Here’s a little tip on how to create virtualenv with specific version of Python.

+ +

In case you are using MacOS Mojave and you are installing Python3.6 from brew bottle you might have a problem with pip, here’s a solution that might help.

+ +

With time some of you may want to install multiple Python versions with multiple virtual environments per version. When this moment comes I strongly recommend swithing to pyenv and pyenv-virtualenv .

+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2022/10/28/arduino-ov7670.html b/2022/10/28/arduino-ov7670.html new file mode 100644 index 0000000..b163b24 --- /dev/null +++ b/2022/10/28/arduino-ov7670.html @@ -0,0 +1,1585 @@ + + + +使用 Arduino 和 OV7670(不含 FIFO) 拍摄图像 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

使用 Arduino 和 OV7670(不含 FIFO) 拍摄图像

  + +
+
+ + +
+

本文章是对 Arduino Camera (OV7670) Tutorial 的一片不原汁原味的翻译. +中文是按照我自己的理解写出来的, 更多的当个笔记. 如果有错误的地方请参考英文原文.

+
+ +

所需硬件

+ +
    +
  1. OV7670 摄像头模块(不需要 FIFO)
  2. +
  3. Arduino Uno R3
  4. +
  5. 面包板以及连接线
  6. +
  7. USB 打印口线(Arduino 通过 USART 向 PC 发送拍摄到的图片)
  8. +
+ +

简述

+ +

在运动检测, 障碍物识别, 无人机和机器人领域, 经常会需要使用摄像头来捕获图像. 在这些场景中, 因为 Arduino 羸弱的性能不足以处理图片和视频, 比较好的方案是使用 Raspberry Pi 或者 BeagleBone Black. 然而如果项目不需要高分辨率图像, 那么 OV7670 模块可能也适合. 本片教程介绍此相机, 并教你如何获取一张 VGA 尺寸(大约应该是 640x480 大小)图像.

+ + + +

接线图

+ +

下面是一张 OV7670 模块图和模块的引脚图:

+ +
+
+ OV7670 模块实拍 +
+ OV7670 模块实拍 +
+
+
+ 引脚 +
+ 引脚 +
+
+
+ +

可以看到, 摄像头模块有 18 个引脚, 分别如下:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameFunctionNameFunction
3V3Positive power supply pinD7Video parallel output bit 7
GNDGround pinD6Video parallel output bit 6
SDIOCSerial clockD5Video parallel output bit 5
SDIODSerial dataD4Video parallel output bit 4
VSYNCVertical syncD3Video parallel output bit 3
HREFHorizontal syncD2Video parallel output bit 2
PLCKPixel clock outputD1Video parallel output bit 1
XCLKSystem clock outputD0Video parallel output bit 0
RESETReset (active low) pinPWDNPower down (active high) pin
+ +

部分 OV7670 模块可能只有 16 个引脚, 这种一般是没有 RESETPWON. 我这边手头上是 18 脚的模块, 因此此教程后续就以 18 脚讲解.

+ +

如果你稍微注意一下就会发现 OV7670 模块有串行数据(serial data)和时钟(serial clock)引脚. 这意味着 Arduino 可以使用 I2C 协议和它通讯(实际上 OV 系列的芯片使用的是 I2C 的一个变种, 叫做 SCCB(Serial Camera Control Bus)). 另外模块使用的电压是 3.3V, 因此在向 OV7670 发送数据的时候, 需要一个分压电阻.

+ +

下面是具体的接线图:

+ +

+ +

获取图像数据

+ +

Arduino 可以给摄像头发送配置命令, 并接收拍摄到的图像数据, 但是它没法方便的把拍摄到的图像显示出来, 这里采用的方案是通过 Arduino 的 USB 端口将数据发送到 PC 上, 再使用相关的软件吧图像显示出来.

+ +

代码

+ +

下面贴出来项目中使用到的代码

+ +
#define F_CPU 16000000UL
+#include "ov7670.h"
+
+#include <avr/interrupt.h>
+#include <avr/io.h>
+#include <avr/pgmspace.h>
+#include <stdint.h>
+#include <util/delay.h>
+#include <util/twi.h>
+/* Configuration: this lets you easily change between different resolutions
+ * You must only uncomment one
+ * no more no less*/
+#define useVga
+// #define useQvga
+// #define useQqvga
+
+static inline void serialWrB(uint8_t dat) {
+  while (!(UCSR0A & (1 << UDRE0)))
+    ;  // wait for byte to transmit
+  UDR0 = dat;
+  while (!(UCSR0A & (1 << UDRE0)))
+    ;  // wait for byte to transmit
+}
+static void StringPgm(const char* str) {
+  do {
+    serialWrB(pgm_read_byte_near(str));
+  } while (pgm_read_byte_near(++str));
+}
+static void captureImg(uint16_t wg, uint16_t hg) {
+  uint16_t lg2;
+#ifdef useQvga
+  uint8_t buf[640];
+#elif defined(useQqvga)
+  uint8_t buf[320];
+#endif
+  StringPgm(PSTR("RDY"));
+  // Wait for vsync it is on pin 3 (counting from 0) portD
+  while (!(PIND & 8))
+    ;  // wait for high
+  while ((PIND & 8))
+    ;  // wait for low
+#ifdef useVga
+  while (hg--) {
+    lg2 = wg;
+    while (lg2--) {
+      while ((PIND & 4))
+        ;  // wait for low
+      UDR0 = (PINC & 15) | (PIND & 240);
+      while (!(PIND & 4))
+        ;  // wait for high
+    }
+  }
+#elif defined(useQvga)
+  /*We send half of the line while reading then half later */
+  while (hg--) {
+    uint8_t *b = buf, *b2 = buf;
+    lg2 = wg / 2;
+    while (lg2--) {
+      while ((PIND & 4))
+        ;  // wait for low
+      *b++ = (PINC & 15) | (PIND & 240);
+      while (!(PIND & 4))
+        ;  // wait for high
+      while ((PIND & 4))
+        ;  // wait for low
+      *b++ = (PINC & 15) | (PIND & 240);
+      UDR0 = *b2++;
+      while (!(PIND & 4))
+        ;  // wait for high
+    }
+    /* Finish sending the remainder during blanking */
+    lg2 = wg / 2;
+    while (!(UCSR0A & (1 << UDRE0)))
+      ;  // wait for byte to transmit
+    while (lg2--) {
+      UDR0 = *b2++;
+      while (!(UCSR0A & (1 << UDRE0)))
+        ;  // wait for byte to transmit
+    }
+  }
+#else
+  /* This code is very similar to qvga sending code except we have even more
+   * blanking time to take advantage of */
+  while (hg--) {
+    uint8_t *b = buf, *b2 = buf;
+    lg2 = wg / 5;
+    while (lg2--) {
+      while ((PIND & 4))
+        ;  // wait for low
+      *b++ = (PINC & 15) | (PIND & 240);
+      while (!(PIND & 4))
+        ;  // wait for high
+      while ((PIND & 4))
+        ;  // wait for low
+      *b++ = (PINC & 15) | (PIND & 240);
+      while (!(PIND & 4))
+        ;  // wait for high
+      while ((PIND & 4))
+        ;  // wait for low
+      *b++ = (PINC & 15) | (PIND & 240);
+      while (!(PIND & 4))
+        ;  // wait for high
+      while ((PIND & 4))
+        ;  // wait for low
+      *b++ = (PINC & 15) | (PIND & 240);
+      while (!(PIND & 4))
+        ;  // wait for high
+      while ((PIND & 4))
+        ;  // wait for low
+      *b++ = (PINC & 15) | (PIND & 240);
+      UDR0 = *b2++;
+      while (!(PIND & 4))
+        ;  // wait for high
+    }
+    /* Finish sending the remainder during blanking */
+    lg2 = 320 - (wg / 5);
+    while (!(UCSR0A & (1 << UDRE0)))
+      ;  // wait for byte to transmit
+    while (lg2--) {
+      UDR0 = *b2++;
+      while (!(UCSR0A & (1 << UDRE0)))
+        ;  // wait for byte to transmit
+    }
+  }
+#endif
+}
+int main(void) {
+  cli();  // disable interrupts
+  /* Setup the 8mhz PWM clock
+   * This will be on pin 11*/
+  DDRB |= (1 << 3);  // pin 11
+  ASSR &= ~(_BV(EXCLK) | _BV(AS2));
+  TCCR2A = (1 << COM2A0) | (1 << WGM21) | (1 << WGM20);
+  TCCR2B = (1 << WGM22) | (1 << CS20);
+  OCR2A = 0;  //(F_CPU)/(2*(X+1))
+  DDRC &= ~15;  // low d0-d3 camera
+  DDRD &= ~252;  // d7-d4 and interrupt pins
+  _delay_ms(3000);
+  // set up twi for 100khz
+  TWSR &= ~3;  // disable prescaler for TWI
+  TWBR = 72;  // set to 100khz
+  // enable serial
+  UBRR0H = 0;
+  UBRR0L = 1;  // 0 = 2M baud rate. 1 = 1M baud. 3 = 0.5M. 7 = 250k 207 is 9600
+               // baud rate.
+  UCSR0A |= 2;  // double speed aysnc
+  UCSR0B = (1 << RXEN0) | (1 << TXEN0);  // Enable receiver and transmitter
+  UCSR0C = 6;  // async 1 stop bit 8bit char no parity bits
+  camInit();
+#ifdef useVga
+  setRes(VGA);
+  setColorSpace(BAYER_RGB);
+  wrReg(0x11, 25);
+#elif defined(useQvga)
+  setRes(QVGA);
+  setColorSpace(YUV422);
+  wrReg(0x11, 12);
+#else
+  setRes(QQVGA);
+  setColorSpace(YUV422);
+  wrReg(0x11, 3);
+#endif
+  /* If you are not sure what value to use here for the divider (register 0x11)
+   * Values I have found to work raw vga 25 qqvga yuv422 12 qvga yuv422 21
+   * run the commented out test below and pick the smallest value that gets a
+   * correct image */
+  while (1) {
+    /* captureImg operates in bytes not pixels in some cases pixels are two
+     * bytes per pixel So for the width (if you were reading 640x480) you would
+     * put 1280 if you are reading yuv422 or rgb565 */
+    /*uint8_t x=63;//Uncomment this block to test divider settings note the
+      other line you need to uncomment do{ wrReg(0x11,x); _delay_ms(1000);*/
+#ifdef useVga
+    captureImg(640, 480);
+#elif defined(useQvga)
+    captureImg(320 * 2, 240);
+#else
+    captureImg(160 * 2, 120);
+#endif
+    //}while(--x);//Uncomment this line to test divider settings
+  }
+}
+
+ +

请注意这部分代码需要使用到 ComputerNerd 提供的 OV7670 的库, 可以在这里下载: https://github.com/kurimawxx00/ov7670-no-ram-arduino-uno

+ +

在 PC 上显示图像的软件, 可以使用下面的 python 代码:

+ +
import sys
+import time
+import serial
+import cv2
+import numpy as np
+
+
+def parse_image(data):
+    image_start_marker = b'*RDY*'
+
+    width = 320
+    height = 240
+
+    # 图像数据的长度是 width*height 个字节
+    size = width * height
+
+    # 删除掉无法处理的数据(可能是读取串口之前的图像脏数据)
+    while data[:5] != image_start_marker and len(data):
+        data = data[1:]
+
+    # 数据长度不足以容纳一张完整的图像(大小加上图片结束标记)
+    # 或者不是一张合法有效的图像
+    if len(data) < (size + 10) or data[:5] != image_start_marker:
+        return data, None
+
+    data = data[6:]
+    buffer = data[:size]
+    data = data[size + 5:]
+
+    img = np.frombuffer(buffer, dtype=np.uint8)
+    img = np.reshape(img, (240, 320))
+
+    return data, img
+
+
+ser = serial.Serial(port=sys.argv[1], baudrate=1024 * 1024)
+
+img_win_name = "OV7670 image"
+img_win = cv2.namedWindow(img_win_name)
+
+data = b""
+while True:
+    snap = ser.read(12000)
+    if len(snap) == 0:
+        continue
+
+    data += snap
+    data, img = parse_image(data)
+    if img is not None:
+        img = cv2.rotate(img, rotateCode=cv2.ROTATE_90_COUNTERCLOCKWISE)
+        cv2.imshow(img_win_name, img)
+        cv2.waitKey(1500)
+
+ +

希望你通过本文已经能为 Arduino Uno 成功设置 OV7670 模块并拍得照片. 不过我还是建议你使用更高级的板子(Raspberry Pi 或者 BeagleBone Black)来做图像方面是事情, 因为 Arduino Uno 的性能在处理拍照这种任务的时候实在是拉胯.

+ +

原作者关于树莓派摄像头的教程

+ +

贴一张成品:

+ +

+ +

参考资料

+ + +
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2022/10/28/hardware-document-reference-copy.html b/2022/10/28/hardware-document-reference-copy.html new file mode 100644 index 0000000..db09e1e --- /dev/null +++ b/2022/10/28/hardware-document-reference-copy.html @@ -0,0 +1,1219 @@ + + + +硬件/电路/单片机/机器人常用资料 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

硬件/电路/单片机/机器人常用资料

  + +
+
+ + +

本文列举了一些常用的硬件数据手册链接和其他比较好的资料, 方便翻阅.

+ +

常用的数据手册

+ + + +

MCU

+ +

Atmel

+ + + +

Espressif

+ + + +

Camera

+ + +
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2023/03/24/sed-and-awk-101-hacks.html b/2023/03/24/sed-and-awk-101-hacks.html new file mode 100644 index 0000000..ea7dec0 --- /dev/null +++ b/2023/03/24/sed-and-awk-101-hacks.html @@ -0,0 +1,1709 @@ + + + +sed and awk 101 hacks 笔记 - 挚爱荒原 + + + + + + + + +
+
+
+ +
+ +
+ +
+ +
+ + + +
+

sed and awk 101 hacks 笔记

  + + +
+ + + + + +
+
+ + + +
+
+

图书在线阅读地址: sed and awk 101 hacks

+
+ +

本文记录 *sed and awk 101 hacks* 一书中学到的知识点备忘.

+ +

sed 部分

+ +
+

本文中的 有些知识并不实用, 如果懵, 不用全都学会, 关键是有机会的时候多用一用, 熟能生巧, 慢慢的就都记住了

+ +

!!! 文中的操作不一定是最简洁的操作, 有些为了演示功能生造出来的使用场景.

+
+ +

本文中使用到的示例文件是系统中的密码文件 passwd:

+ +
root:x:0:0:root:/root:/bin/bash
+bin:x:1:1:bin:/bin:/sbin/nologin
+daemon:x:2:23:daemon:/sbin:/sbin/nologin
+desktop:x:80:80:desktop:/var/lib/menu/kde:/sbin/nologin
+mengqc:x:500:500:mengqc:/home/mengqc:/bin/bash
+
+ + + +

sed 的基本认识

+ +

sed 是一种没有交互的流式编辑器(stream editor), 一般在写脚本的时候用的比较多, 作为文本处理三剑客之一, 如果用的熟练, 在平常工作的时候做一些批量替换批量生成之类的处理也很方便.

+ +

sed 工作的时候, 正常情况下每次读取一行文件内容放到一块叫做 模式空间(pattern space) 的内存区域, 然后 sed 根据给它的指令, 对空间中的文本做编辑/替换等处理操作. 处理过程中并不是它读取到的每一行数据都需要处理, 这时候可以通过指定 地址 约束要处理的文本范围.

+ +

+ +

除了模式空间之外, 还有一块叫做 保持空间(hold space) 的区域, 可以用来暂存数据, sed 提供了专用的命令来操作这两个空间的数据, 通过合理的配合使用, 威力惊人.

+ +

指令详解和使用示例

+ +

先从一个简单的例子开始:

+ +
> cat passwd | sed '3,$s/bash/zsh/'
+root:x:0:0:root:/root:/bin/bash
+bin:x:1:1:bin:/bin:/sbin/nologin
+daemon:x:2:23:daemon:/sbin:/sbin/nologin
+desktop:x:80:80:desktop:/var/lib/menu/kde:/sbin/nologin
+mengqc:x:500:500:mengqc:/home/mengqc:/bin/zsh
+
+ +

上文示例指令会把 passwd 从第三行开始用 bash 作为登录 shell 用户都替换成使用 zsh 登录. 涉及到了 sed 的地址和替换两个知识点, 简言之命令使用 3,$ 约束要处理的数据范围, 然后使用 s 命令执行替换.

+ +

地址介绍

+ +

首先介绍地址相关的概念, 所谓地址是用来表示文件中数据范围的行号或者模式或者特别符号又或者是这些的组合, 比方说上面的 3,$ 就组合了行号和 $ 标记来表示从第三行到文件末尾的范围.

+ +

可能的地址写法有以下几种:

+ +
    +
  1. 仅一个数字 - 表示只处理行号等于数字的这一行
  2. +
  3. +first~step 从 first 行开始往后处理, 步长是 step. 也即每隔 step 行处理一次
  4. +
  5. +$ 表示最后一行
  6. +
  7. +/regexp/ 正则表达式匹配的行
  8. +
  9. +\cregexpc 也是正则表达式, 不同的是可以指定 / 变为任意字符, 这样在匹配路径的时候写起来会简单一些
  10. +
  11. +m,n 表示从 m 行到 n 行. 如果 m 大于 n, 那么 m 行依然会被匹配一次
  12. +
+ +

GNU Sed 的一些特别支持:

+ +
    +
  1. +0,addr2 几乎和 1,addr2 一样, 区别在于是第一行和 addr2 匹配的时候, 前者只能匹配 1 行, 后者会匹配到文件中部能匹配到 addr2 的地方
  2. +
  3. +addr1,+N 匹配从 addr1 开始之后的 N 行
  4. +
  5. +addr1,~Naddr1 开始匹配, 每隔 N 行匹配到一次
  6. +
+ +

替换

+ +

s 命令用于执行替换操作, 一般的用法是 s/pattern/content/flag, 其中 pattern 是要替换的模式, content 是要替换的新内容, flag 是给替换命令的一些标记, 比如可以控制替换的次数, 是否忽略大小写等.

+ +

一般而言, pattern 部分是需要用正则表达式表达的, sed 可以支持最多 9 个正则表达式分组(分组就是用 \(\) 包裹起来的部分), 分组可以被反向引用(back references), 反向引用可以出现在两个地方:

+ +
    +
  • 首先是 content 中可以通过 \N 的形式引用某个特定编号的分组(从 1 开始计数, 0 是 pattern 匹配的全部内容)
  • +
  • +pattern 中也可以使用反向引用自己的分组, 比方说 \(abc\)\1 可以匹配 abcabc +
  • +
+ +

值得一提的是 content 中的 & 字符也可以表示全部匹配的内容.

+ +

flag 是用来微调替换行为的, 常见的 flag 有:

+ +
    +
  1. +I 忽略大小写, 不用 i 是因为 i 在 sed 里面是 insert 指令
  2. +
  3. +数字 表示替换第 数字 次出现的 pattern +
  4. +
  5. +g 全局替换
  6. +
+ +

看一个涉及上面概念的复杂例子:

+ +
> cat passwd | sed 's/\(.*\):x:\(.*\):\2:/\U\1\E ==> &/g'
+ROOT ==> root:x:0:0:root:/root:/bin/bash
+BIN ==> bin:x:1:1:bin:/bin:/sbin/nologin
+daemon:x:2:23:daemon:/sbin:/sbin/nologin
+DESKTOP ==> desktop:x:80:80:desktop:/var/lib/menu/kde:/sbin/nologin
+MENGQC ==> mengqc:x:500:500:mengqc:/home/mengqc:/bin/bash
+
+ +

最后在 content 中可以使用 \U, \L 调整大小写:

+ +
> echo 'ThisIsClassName' | sed 's/^\(.\)/\l\1/;s/\([A-Z]\)/_\l\1/g'
+this_is_class_name
+
+ +

关于替换分隔符:

+ +

sed 的替换分隔符不止可以使用 /, 事实上我们可以用任何字符当做分隔符, 比放在替换路径的场景中, 可以使用 # 当做分隔符:

+ +
> echo '/etc/passwd' | sed 's#/etc#/opt/etc#'
+/opt/etc/passwd
+
+ +

更多

+ +

正则表达式的更多知识:

+ +
    +
  • 开始结束 ^, $ +
  • +
  • 匹配字符 ., *, \+, \?,
  • +
  • 转义 \ +
  • +
  • 字符集 [] ~~~ [xxx], [x-y] +
  • +
  • +\b 单词边界, 可以用来表示单词的开始和结束
  • +
  • 或表达式 | +
  • +
  • _{m}
  • +
  • _{m,n}
  • +
  • _{m,}
  • +
+ +

编辑/修改/删除命令

+ +

像 vim 一样, sed 有用于往前插入(insert)内容的 i 命令和往后追加(append)内容的 a 命令和用于修改(change)的 c命令.

+ +
> cat passwd | sed '1asecond line'
+root:x:0:0:root:/root:/bin/bash
+second line
+bin:x:1:1:bin:/bin:/sbin/nologin
+daemon:x:2:23:daemon:/sbin:/sbin/nologin
+desktop:x:80:80:desktop:/var/lib/menu/kde:/sbin/nologin
+mengqc:x:500:500:mengqc:/home/mengqc:/bin/bash
+
+ +
> cat passwd | sed '1ifirst line'
+first line
+root:x:0:0:root:/root:/bin/bash
+bin:x:1:1:bin:/bin:/sbin/nologin
+daemon:x:2:23:daemon:/sbin:/sbin/nologin
+desktop:x:80:80:desktop:/var/lib/menu/kde:/sbin/nologin
+mengqc:x:500:500:mengqc:/home/mengqc:/bin/bash
+
+ +
> cat passwd | sed '1cchange content'
+change content
+bin:x:1:1:bin:/bin:/sbin/nologin
+daemon:x:2:23:daemon:/sbin:/sbin/nologin
+desktop:x:80:80:desktop:/var/lib/menu/kde:/sbin/nologin
+mengqc:x:500:500:mengqc:/home/mengqc:/bin/bash
+
+ +

模式空间和保持空间操作

+ +

保持空间一般用来暂存数据, 通过对模式空间和保持空间的合理安排, 可以实现很多复杂功能.

+ +

g/G 命令是 get 命令的缩写, 表示从保持空间拷贝/追加数据到模式空间, h/H 命令是 hold 命令的缩写, 表示从模式空间拷贝/追加数据到保持空间.

+ +

举一个例子, 把相邻两行合并成一行, 并交换顺序:

+ +
> seq 1 11 | sed -n 'h;n;G;s/\n/ /p'
+2 1
+4 3
+6 5
+8 7
+10 9
+
+ +
+

思考:

+ +
    +
  1. 上面的例子 11 为什么没有打印出来, 想打印的话要怎么做?
  2. +
  3. 上面的问题, 三行应该怎么做?
  4. +
+
+ +

x 命令交换模式空间/保持空间, 还是上面的例子, 假如说不想对调两行的位置了, 可以再用一个 x 命令处理一下:

+ +
> seq 1 11 | sed -n 'h;n;x;G;s/\n/ /p'
+1 2
+3 4
+5 6
+7 8
+9 10
+
+ +

流程跳转

+ +

b 命令用于直接跳转到标号处. 如果不指定标号, 直接跳到脚本结束位置, 开始下次处理循环.

+ +
> cat play
+剧名:白夜追凶
+评分:9.0
+剧名:秘密深林
+评分:9.3
+剧名:权利的游戏第七季
+评分:9.3
+剧名:请回答 1988
+评分:9.6
+
+ +
✗ cat play | sed -n 'N;s/\n/, /;/1988/!b label;s/^/---/;:label;p'
+剧名:白夜追凶, 评分:9.0
+剧名:秘密深林, 评分:9.3
+剧名:权利的游戏第七季, 评分:9.3
+---剧名:请回答 1988, 评分:9.6
+
+ +

不指定标号的例子:

+ +
> cat play | sed -n 'N;s/\n/, /;/1988/!b;s/^/---/;p'
+---剧名:请回答 1988, 评分:9.6
+
+ +

t 命令也是跳转到标号处, 但是有判断之前执行的替换操作是成功的这个前提才会跳转

+ +

下面的例子利用跳转, 全局替换了文本中的 o 字符, 实现了替换命令里面的 g 标志功能.

+ +
> cat passwd | sed ':x;s/o/O/;tx'
+rOOt:x:0:0:rOOt:/rOOt:/bin/bash
+bin:x:1:1:bin:/bin:/sbin/nOlOgin
+daemOn:x:2:23:daemOn:/sbin:/sbin/nOlOgin
+desktOp:x:80:80:desktOp:/var/lib/menu/kde:/sbin/nOlOgin
+mengqc:x:500:500:mengqc:/hOme/mengqc:/bin/bash
+
+ +

再来一个有点绕的:

+ +
> cat play | sed -n 'N;s/\n/, /;:x;/1988/s/^/-/;/----/!tx;p'
+剧名:白夜追凶, 评分:9.0
+剧名:秘密深林, 评分:9.3
+剧名:权利的游戏第七季, 评分:9.3
+----剧名:请回答 1988, 评分:9.6
+
+ +

其他命令

+ +
    +
  1. +e 将 pattern space 里面的内容当成命令执行, 并将执行结果当做 pattern space 的内容.
  2. +
  3. +*.sed 脚本中的头两个字符如果是 #n, sed 将会自动使用 -n 选项执行
  4. +
  5. 注意 l 命令自带 print 效果
  6. +
  7. += 命令输出行号
  8. +
  9. +y 命令用于将两个字符列表按照位置转换(transform)
  10. +
  11. sed 可以一次操作多个文本文件
  12. +
  13. +q 在处理完成之后直接退出 sed 程序
  14. +
  15. +w 命令用于将模式空间写到文件, r 命令用于将文件读到<标准输出还是模式空间?看上去不像是模式空间>
  16. +
  17. +P 多行模式空间的情况下, 仅输出第一行
  18. +
  19. +D 多行模式空间的情况下, 仅删除第一行, 且不会将下一行读取到模式空间(对比 d), 并重新从头开始对模式空间内容执行命令
  20. +
+ +

其他

+ +

11 行丢失的问题:

+ +
> {seq 1 11; seq 1 5 | sed 's/^._$/AAAAAA/'} | sed -n 'h;n;G;s/\n/ /p' | sed 's/AAAAAA\s_//g' | sed '/^$/d'
+2 1
+4 3
+6 5
+8 7
+10 9
+11
+
+ +

awk 部分

+ +

Awk 命令的基本格式

+ +
awk -Fs '/pattern/ {action}' input-file
+# OR
+awk -Fs '{action}' intput-file
+# OR
+awk -Fs -f myscript.awk input-file
+
+ +

Example:

+ +
awk -F: '/mail/ {print $1}' /etc/passwd
+
+ +
    +
  1. awk 的程序结构 BEGIN, body, END block
  2. +
+ +

A typical awk program has following three blocks. +BEGIN Block +Syntax of begin block: +BEGIN { awk-commands } +The begin block gets executed only once at the beginning, before awk starts executing the body block for all the lines in the input file. +The begin block is a good place to print report headers, and initialize variables. +You can have one or more awk commands in the begin block. +The keyword BEGIN should be specified in upper case. +Begin block is optional. +Body Block +Syntax of body block: +/pattern/ {action} +The body block gets executed once for every line in the input file. +If the input file has 10 records, the commands in the body block will be executed 10 times (once for each record in the input file). +There is no keyword for the body block. We discussed pattern and action previously. +END Block +Syntax of end block: +END { awk-commands } +The end block gets executed only once at the end, after awk completes executing the body block for all the lines in the input-file. +• The end block is a good place to print a report footer and do any clean-up activities. +• You can have one or more awk commands in the end block. +• The keyword END should be specified in upper case. +• End block is optional. +awk work example Awk work example The following simple example shows the three awk blocks in action. +$ awk ‘BEGIN { FS=”:”;print “—header—” }
+/mail/ {print $1}
+END { print “—footer—”}’ /etc/passwd +—header— +mail +mailnull +—footer— +Note: When you have a very long command, you can either type is on a single line, or split it to multiple lines by specifying a \ at the end of each line. The above example is typed in 3 lines with a \ at the end of line 1 and line 2. In the above example: +BEGIN { FS=”:”;print “—header—” } is the begin block, that sets the field separator variable FS (more on this later), and prints the header. This gets executed only once before the body loop. +/mail/ {print $1} is the body loop, that contains a pattern and an action. i.e. This searches for the keyword “mail” in the input file and prints the 1st field. +END { print “—footer—”}’ is the end block, that prints the footer. +/etc/passwd is the input file. The body loop gets executed for every records in this file. +Instead of executing the above simple example from the command line, you can also execute it from a file. First, create the following myscript.awk file that contains the begin, body, and end loop: +$ vi myscript.awk +BEGIN { +FS=”:” +print “—header—” +} +/mail/ { +print $1 +} + END { + print “—footer—” +} +Next, execute the myscript.awk as shown below for the input file /etc/passwd: +$ awk -f myscript.awk /etc/passwd +—header— +mail +mailnull +—footer— +Please note that a comment inside a awk script starts with #. If you are writing a complex awk script, follow the best practice: write enough comments inside the *.awk file so that it will be easier for you to understand when you look at the file later. Following are some random simple examples that show you various combinations of awk blocks. Only the body block: +awk -F: ‘{ print $1 }’ /etc/passwd +Begin, body, and end block: +awk -F: ‘BEGIN { printf “username\n——\n”}
+{ print $1 }
+END { print “——” }’ /etc/passwd +Begin, and body block: +awk -F: ‘BEGIN { print “UID”} { print $3 }’ /etc/passwd +A Note on using only a BEGIN Block: +Specifying only the begin block is valid awk syntax. When you don’t specify a body loop, there is no point in specifying a input file, since only the body loop gets executed for the lines in the input file. So, use only the BEGIN block when you want to use an awk program to do things not related to file processing. In many of our examples below, we’ll have only the BEGIN block, to explain how some of the awk programming components work. You can use this idea for anything that you see fit. A simple begin only example: +$ awk ‘BEGIN { print “Hello World!” }’ +Hello World! +Multiple Input Files +Please note that you can specify multiple input files. If you specify two input files, first the body block will be executed for all the lines in input-file1, next the body block will be executed for all the lines in input-file2. Multiple input file example: +$ awk ‘BEGIN { FS=”:”;print “—header—” }
+/mail/ {print $1}
+END { print “—footer—”}’ /etc/passwd /etc/group +—header— +mail +mailnull +mail +mailnull +—footer— +Please note that the BEGIN block and the END block will be executed only once, even when you specify multiple input-files.

+
    +
  1. Print Command +By default, the awk print command (without any argument) prints the full record as shown. The following example is equivalent to “cat employee.txt” command. +$ awk ‘{print}’ employee.txt +101,John Doe,CEO +102,Jason Smith,IT Manager +103,Raj Reddy,Sysadmin +104,Anand Ram,Developer +105,Jane Miller,Sales Manager +You can also print specific fields in a record by passing $field-number as a print command argument. The following example is supposed to print only the employee name (field number 2) of every record. +$ awk ‘{print $2}’ employee.txt +Doe,CEO +Smith,IT +Reddy,Sysadmin +Ram,Developer +Miller,Sales +Wait. It didn’t work as expected. It printed from the last name until the end of the record. This is because the default field delimiter in Awk is space. Awk did exactly what we asked; it did print the 2nd field considering space as a delimiter. When the default space is used as delimiter, “101,John” became field-1 and “Doe,CEO” became field- 2 of the 1st record. So, the above awk example printed “Doe,CEO” as field-2. To solve this issue, we should instruct Awk to use comma (,) as field delimiter. Use option -F to indicate the field separator. +awk -F ‘,’ ‘{print $2}’ employee.txt +John Doe +Jason Smith +Raj Reddy +Anand Ram +Jane Miller +When there is only one character used for delimiter, any of the following forms works, i.e. you can specify the field delimiter within single quotes, or double quotes, or without any quotes as shown below. +awk -F ‘,’ ‘{print $2}’ employee.txt +awk -F “,” ‘{print $2}’ employee.txt +awk -F, ‘{print $2}’ employee.txt +Note: You can also use the FS variable for this purpose. We’ll review that in the awk built-in variables section. A simple report that prints employee name and title with a header and footer: +$ awk -F ‘,’ ‘BEGIN
    +{ print “————-\nName\tTitle\n————-“}
    +{ print $2,”\t”,$3;}
    +END { print “————-“; }’ employee.txt
  2. +
+ +
+ +

Name Title

+ +

John Doe CEO +Jason Smith IT Manager +Raj Reddy Sysadmin +Anand Ram Developer +Jane Miller Sales Manager

+ +
+ +

In the above report the fields are not aligned properly. We’ll look at how to do that in later sections. The above example does show how you can use BEGIN to print a header, and END to print a footer. Please note that field $0 represents the whole record. Both of the following examples are the same; each prints the whole lines from employee.txt. +awk ‘{print}’ employee.txt +awk ‘{print $0}’ employee.txt

+
    +
  1. AWK Pattern Matching +You can execute awk commands only for lines that match a particular pattern. For example, the following prints the names and titles of the Managers: +$ awk -F ‘,’ ‘/Manager/ {print $2, $3}’ employee.txt +Jason Smith IT Manager +Jane Miller Sales Manager +The following example prints the employee name whose Emp id is 102: +$ awk -F ‘,’ ‘/^102/ {print “Emp id 102 is”, $2}’ employee.txt +Emp id 102 is Jason Smith
  2. +
+
+ + +
+ + +
+
+ + +
+
+ +
+
+ +
+ + + + + + +
+ + + + diff --git a/2023/04/14/meta-programming.html b/2023/04/14/meta-programming.html new file mode 100644 index 0000000..bad0390 --- /dev/null +++ b/2023/04/14/meta-programming.html @@ -0,0 +1,1336 @@ + + + +python 元编程 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

python 元编程

  + +
+
+ + +

本文讲述了 python 中 class 的一些细节. 包含:

+ +
    +
  1. class 定义和 class 对象
  2. +
  3. class 实例对象
  4. +
+ +

__new__ 函数和 __call__ 函数

+ +

先从简单的开始, 比如以下代码:

+ + + +
class hello():
+    print('hello 类')
+
+    a = 'aaaa'
+
+    def __new__(cls, *args, **kwargs):
+        print('hello 运行__new__函数')
+        print("cls", cls)
+        print("args", args)
+        print("kwargs", kwargs)
+        print("=" * 20)
+        res = object.__new__(cls)
+        return res
+
+    def __call__(self, *args, **kwargs):
+        print('hello 运行__call__函数')
+        print("self", self)
+        print("args", args)
+        print("kwargs", kwargs)
+        print("=" * 20)
+
+
+print('------ execute -----')
+print(type(hello))
+print(hello.a)
+print('A', '-' * 20)
+obj = hello('HI')
+print('B', '-' * 20)
+print(obj)
+print('C', '-' * 20)
+obj()
+print('D', '-' * 20)
+
+ +

输出为:

+ +
hello 类
+------ execute -----
+<class 'type'>
+aaaa
+A --------------------
+hello 运行__new__函数
+cls <class '__main__.hello'>
+args ('HI',)
+kwargs {}
+====================
+B --------------------
+<__main__.hello object at 0x10ee13fd0>
+C --------------------
+hello 运行__call__函数
+self <__main__.hello object at 0x10ee13fd0>
+args ()
+kwargs {}
+====================
+D --------------------
+
+ +

可以看到在 Python 解释器遇到 class 定义的时候, 就会执行 class body 中的语句. 当实例化一个类的时候, __new__ 方法被调用, 如果尝试把实例对象作为函数调用, 则会触发 __call__ 函数的调用.

+ +

接下来给 hello 指定一个 metaclass.

+ +
class mclass(type):
+    def __new__(cls, clsname, bases, dct):
+        print('mclass - 运行__new__函数')
+        print("cls", cls)
+        print("clsname", clsname)
+        print("bases", bases)
+        print("dct", dct)
+        print("=" * 20)
+        res = super().__new__(cls, clsname, bases, dct)
+        return res
+
+    def __call__(self, *args, **kwargs):
+        print('mclass 对象实例运行__call__函数')
+        print("args", args)
+        print("kwargs", kwargs)
+        print("=" * 20)
+        return self
+
+class world(metaclass=mclass):
+    print("world class")
+
+class hello(metaclass=mclass):
+    ...
+
+ +

这次再执行脚本, 输出如下:

+ +
hello 类
+mclass - 运行__new__函数
+cls <class '__main__.mclass'>
+clsname hello
+bases ()
+dct {'__module__': '__main__', '__qualname__': 'hello', 'a': 'aaaa', '__new__': <function hello.__new__ at 0x10856e3b0>, '__call__': <function hello.__call__ at 0x108622cb0>}
+====================
+world class
+mclass - 运行__new__函数
+cls <class '__main__.mclass'>
+clsname world
+bases ()
+dct {'__module__': '__main__', '__qualname__': 'world'}
+====================
+------ execute -----
+<class '__main__.mclass'>
+aaaa
+A --------------------
+mclass 对象实例运行__call__函数
+args ('HI',)
+kwargs {}
+====================
+B --------------------
+<class '__main__.hello'>
+C --------------------
+mclass 对象实例运行__call__函数
+args ()
+kwargs {}
+====================
+D --------------------
+
+ +

可以看到几点不同:

+ +
    +
  1. mclass 的 __new__ 在解释器遇到 world 或者 hello 类的定义的时候就触发了, 这里应该理解为, 解释器需要使用 metaclass 实例来生成类型对象, 因此就需要在遇到 metaclass=mclass 时候将 mclass 实例化, 所以 mclass 的 __new__ 函数被触发了 2 次
  2. +
  3. hello 类的类型现在变成了 mclass, 这主要是因为指定了元类之后, 类对象就会使用这个元类生成, 因此类型就不再是 type 了(type 本身也是一种元类)
  4. +
  5. hello 类实例化的时候, 触发了元类的 __call__ 函数, 等到再次尝试以函数方式调用实例时, 又触发了元类 __call__ 调用.
  6. +
+ +
+

TODO: 第三点是为啥?

+
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2023/04/14/metaclass.html b/2023/04/14/metaclass.html new file mode 100644 index 0000000..d68070c --- /dev/null +++ b/2023/04/14/metaclass.html @@ -0,0 +1,1742 @@ + + + +python 的 metaclass 到底是什么 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

python 的 metaclass 到底是什么

  + +
+
+ + +
+

文章地址: https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python?answertab=votes#tab-top

+
+ +

Classes as objects

+ +

Before understanding metaclasses, it helps to understand Python classes more deeply. Python has a very peculiar idea of what classes are, which it borrows from the Smalltalk language.

+ +

In most languages, classes are just pieces of code that describe how to produce an object. That is somewhat true in Python too:

+ +
>>> class ObjectCreator(object):
+...       pass
+
+>>> my_object = ObjectCreator()
+>>> print(my_object)
+<__main__.ObjectCreator object at 0x8974f2c>
+
+ + + +

But classes are more than that in Python. Classes are objects too.

+ +

Yes, objects.

+ +

When a Python script runs, every line of code is executed from top to bottom. When the Python interpreter encounters the class keyword, Python creates an object out of the “description” of the class that follows. Thus, the following instruction

+ +
>>> class ObjectCreator(object):
+...       pass
+
+ +

…creates an object with the name ObjectCreator!

+ +

This object (the class) is itself capable of creating objects (called instances).

+ +

But still, it’s an object. Therefore, like all objects:

+ +

you can assign it to a variable1

+ +
JustAnotherVariable = ObjectCreator
+
+ +

you can attach attributes to it

+ +
ObjectCreator.class_attribute = 'foo'
+
+ +

you can pass it as a function parameter

+ +
print(ObjectCreator)
+
+ +

Note that merely assigning it to another variable doesn’t change the class’s name, i.e.,

+ +
>>> print(JustAnotherVariable)
+<class '__main__.ObjectCreator'>
+
+>>> print(JustAnotherVariable())
+<__main__.ObjectCreator object at 0x8997b4c>
+
+ +

Creating classes dynamically

+ +

Since classes are objects, you can create them on the fly, like any object.

+ +

First, you can create a class in a function using class:

+ +
>>> def choose_class(name):
+...     if name == 'foo':
+...         class Foo(object):
+...             pass
+...         return Foo # return the class, not an instance
+...     else:
+...         class Bar(object):
+...             pass
+...         return Bar
+...
+>>> MyClass = choose_class('foo')
+>>> print(MyClass) # the function returns a class, not an instance
+<class '__main__.Foo'>
+>>> print(MyClass()) # you can create an object from this class
+<__main__.Foo object at 0x89c6d4c>
+
+ +

But it’s not so dynamic, since you still have to write the whole class yourself.

+ +

Since classes are objects, they must be generated by something.

+ +

When you use the class keyword, Python creates this object automatically. But as with most things in Python, it gives you a way to do it manually.

+ +

Remember the function type? The good old function that lets you know what type an object is:

+ +
>>> print(type(1))
+<type 'int'>
+>>> print(type("1"))
+<type 'str'>
+>>> print(type(ObjectCreator))
+<type 'type'>
+>>> print(type(ObjectCreator()))
+<class '__main__.ObjectCreator'>
+
+ +

Well, type has also a completely different ability: it can create classes on the fly. type can take the description of a class as parameters, and return a class.

+ +

(I know, it’s silly that the same function can have two completely different uses according to the parameters you pass to it. It’s an issue due to backward compatibility in Python)

+ +

type works this way:

+ +
type(name, bases, attrs)
+
+ +

Where:

+ +
    +
  • name: name of the class
  • +
  • bases: tuple of the parent class (for inheritance, can be empty)
  • +
  • attrs: dictionary containing attributes names and values
  • +
+ +

e.g.:

+ +
>>> class MyShinyClass(object):
+...       pass
+
+ +

can be created manually this way:

+ +
>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
+>>> print(MyShinyClass)
+<class '__main__.MyShinyClass'>
+>>> print(MyShinyClass()) # create an instance with the class
+<__main__.MyShinyClass object at 0x8997cec>
+
+ +

You’ll notice that we use MyShinyClass as the name of the class and as the variable to hold the class reference. They can be different, but there is no reason to complicate things.

+ +

type accepts a dictionary to define the attributes of the class. So:

+ +
>>> class Foo(object):
+...       bar = True
+
+ +

Can be translated to:

+ +
>>> Foo = type('Foo', (), {'bar':True})
+
+ +

And used as a normal class:

+ +
>>> print(Foo)
+<class '__main__.Foo'>
+>>> print(Foo.bar)
+True
+>>> f = Foo()
+>>> print(f)
+<__main__.Foo object at 0x8a9b84c>
+>>> print(f.bar)
+True
+
+ +

And of course, you can inherit from it, so:

+ +
>>>   class FooChild(Foo):
+...         pass
+
+ +

would be:

+ +
>>> FooChild = type('FooChild', (Foo,), {})
+>>> print(FooChild)
+<class '__main__.FooChild'>
+>>> print(FooChild.bar) # bar is inherited from Foo
+True
+
+ +

Eventually, you’ll want to add methods to your class. Just define a function with the proper signature and assign it as an attribute.

+ +
>>> def echo_bar(self):
+...       print(self.bar)
+...
+>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
+>>> hasattr(Foo, 'echo_bar')
+False
+>>> hasattr(FooChild, 'echo_bar')
+True
+>>> my_foo = FooChild()
+>>> my_foo.echo_bar()
+True
+
+ +

And you can add even more methods after you dynamically create the class, just like adding methods to a normally created class object.

+ +
>>> def echo_bar_more(self):
+...       print('yet another method')
+...
+>>> FooChild.echo_bar_more = echo_bar_more
+>>> hasattr(FooChild, 'echo_bar_more')
+True
+
+ +

You see where we are going: in Python, classes are objects, and you can create a class on the fly, dynamically.

+ +

This is what Python does when you use the keyword class, and it does so by using a metaclass.

+ +

What are metaclasses (finally)

+ +

Metaclasses are the ‘stuff’ that creates classes.

+ +

You define classes in order to create objects, right?

+ +

But we learned that Python classes are objects.

+ +

Well, metaclasses are what create these objects. They are the classes’ classes, you can picture them this way:

+ +
MyClass = MetaClass()
+my_object = MyClass()
+
+ +

You’ve seen that type lets you do something like this:

+ +
MyClass = type('MyClass', (), {})
+
+ +

It’s because the function type is in fact a metaclass. type is the metaclass Python uses to create all classes behind the scenes.

+ +

Now you wonder “why the heck is it written in lowercase, and not Type?”

+ +

Well, I guess it’s a matter of consistency with str, the class that creates strings objects, and int the class that creates integer objects. type is just the class that creates class objects.

+ +

You see that by checking the __class__ attribute.

+ +

Everything, and I mean everything, is an object in Python. That includes integers, strings, functions and classes. All of them are objects. And all of them have been created from a class:

+ +
>>> age = 35
+>>> age.__class__
+<type 'int'>
+>>> name = 'bob'
+>>> name.__class__
+<type 'str'>
+>>> def foo(): pass
+>>> foo.__class__
+<type 'function'>
+>>> class Bar(object): pass
+>>> b = Bar()
+>>> b.__class__
+<class '__main__.Bar'>
+
+ +

Now, what is the __class__ of any __class__ ?

+ +
>>> age.__class__.__class__
+<type 'type'>
+>>> name.__class__.__class__
+<type 'type'>
+>>> foo.__class__.__class__
+<type 'type'>
+>>> b.__class__.__class__
+<type 'type'>
+
+ +

So, a metaclass is just the stuff that creates class objects.

+ +

You can call it a ‘class factory’ if you wish.

+ +

type is the built-in metaclass Python uses, but of course, you can create your own metaclass.

+ +

The __metaclass__ attribute

+ +

In Python 2, you can add a __metaclass__ attribute when you write a class (see next section for the Python 3 syntax):

+ +
class Foo(object):
+    __metaclass__ = something...
+    [...]
+
+ +

If you do so, Python will use the metaclass to create the class Foo.

+ +

Careful, it’s tricky.

+ +

You write class Foo(object) first, but the class object Foo is not created in memory yet.

+ +

Python will look for __metaclass__ in the class definition. If it finds it, it will use it to create the object class Foo. If it doesn’t, it will use type to create the class.

+ +

Read that several times.

+ +

When you do:

+ +
class Foo(Bar):
+    pass
+
+ +

Python does the following:

+ +

Is there a __metaclass__ attribute in Foo?

+ +

If yes, create in-memory a class object (I said a class object, stay with me here), with the name Foo by using what is in __metaclass__.

+ +

If Python can’t find __metaclass__, it will look for a __metaclass__ at the MODULE level, and try to do the same (but only for classes that don’t inherit anything, basically old-style classes).

+ +

Then if it can’t find any __metaclass__ at all, it will use the Bar’s (the first parent) own metaclass (which might be the default type) to create the class object.

+ +
+

TODO: 下面这段什么意思? +Be careful here that the __metaclass__ attribute will not be inherited, the metaclass of the parent (Bar.__class__) will be. If Bar used a __metaclass__ attribute that created Bar with type() (and not type.__new__()), the subclasses will not inherit that behavior.

+
+ +

Now the big question is, what can you put in __metaclass__?

+ +

The answer is something that can create a class.

+ +

And what can create a class? type, or anything that subclasses or uses it.

+ +

Metaclasses in Python 3

+ +

The syntax to set the metaclass has been changed in Python 3:

+ +
class Foo(object, metaclass=something):
+    ...
+
+ +

i.e. the __metaclass__ attribute is no longer used, in favor of a keyword argument in the list of base classes.

+ +

The behavior of metaclasses however stays largely the same.

+ +

One thing added to metaclasses in Python 3 is that you can also pass attributes as keyword-arguments into a metaclass, like so:

+ +
class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
+    ...
+
+ +

Read the section below for how Python handles this.

+ +

Custom metaclasses

+ +

The main purpose of a metaclass is to change the class automatically, when it’s created.

+ +

You usually do this for APIs, where you want to create classes matching the current context.

+ +

Imagine a stupid example, where you decide that all classes in your module should have their attributes written in uppercase. There are several ways to do this, but one way is to set __metaclass__ at the module level.

+ +

This way, all classes of this module will be created using this metaclass, and we just have to tell the metaclass to turn all attributes to uppercase.

+ +

Luckily, __metaclass__ can actually be any callable, it doesn’t need to be a formal class (I know, something with ‘class’ in its name doesn’t need to be a class, go figure… but it’s helpful).

+ +

So we will start with a simple example, by using a function.

+ +
# the metaclass will automatically get passed the same argument
+# that you usually pass to `type`
+def upper_attr(future_class_name, future_class_parents, future_class_attrs):
+    """
+      Return a class object, with the list of its attribute turned
+      into uppercase.
+    """
+    # pick up any attribute that doesn't start with '__' and uppercase it
+    uppercase_attrs = {
+        attr if attr.startswith("__") else attr.upper(): v
+        for attr, v in future_class_attrs.items()
+    }
+
+    # let `type` do the class creation
+    return type(future_class_name, future_class_parents, uppercase_attrs)
+
+__metaclass__ = upper_attr # this will affect all classes in the module
+
+class Foo(): # global __metaclass__ won't work with "object" though
+    # but we can define __metaclass__ here instead to affect only this class
+    # and this will work with "object" children
+    bar = 'bip'
+
+ +

Let’s check:

+ +
>>> hasattr(Foo, 'bar')
+False
+>>> hasattr(Foo, 'BAR')
+True
+>>> Foo.BAR
+'bip'
+
+ +

Now, let’s do exactly the same, but using a real class for a metaclass:

+ +
# remember that `type` is actually a class like `str` and `int`
+# so you can inherit from it
+class UpperAttrMetaclass(type):
+    # __new__ is the method called before __init__
+    # it's the method that creates the object and returns it
+    # while __init__ just initializes the object passed as parameter
+    # you rarely use __new__, except when you want to control how the object
+    # is created.
+    # here the created object is the class, and we want to customize it
+    # so we override __new__
+    # you can do some stuff in __init__ too if you wish
+    # some advanced use involves overriding __call__ as well, but we won't
+    # see this
+    def __new__(upperattr_metaclass, future_class_name,
+                future_class_parents, future_class_attrs):
+        uppercase_attrs = {
+            attr if attr.startswith("__") else attr.upper(): v
+            for attr, v in future_class_attrs.items()
+        }
+        return type(future_class_name, future_class_parents, uppercase_attrs)
+
+ +

Let’s rewrite the above, but with shorter and more realistic variable names now that we know what they mean:

+ +
class UpperAttrMetaclass(type):
+    def __new__(cls, clsname, bases, attrs):
+        uppercase_attrs = {
+            attr if attr.startswith("__") else attr.upper(): v
+            for attr, v in attrs.items()
+        }
+        return type(clsname, bases, uppercase_attrs)
+
+ +

You may have noticed the extra argument cls. There is nothing special about it: __new__ always receives the class it’s defined in, as the first parameter. Just like you have self for ordinary methods which receive the instance as the first parameter, or the defining class for class methods.

+ +

But this is not proper OOP. We are calling type directly and we aren’t overriding or calling the parent’s __new__. Let’s do that instead:

+ +
class UpperAttrMetaclass(type):
+    def __new__(cls, clsname, bases, attrs):
+        uppercase_attrs = {
+            attr if attr.startswith("__") else attr.upper(): v
+            for attr, v in attrs.items()
+        }
+        return type.__new__(cls, clsname, bases, uppercase_attrs)
+
+ +

We can make it even cleaner by using super, which will ease inheritance (because yes, you can have metaclasses, inheriting from metaclasses, inheriting from type):

+ +
class UpperAttrMetaclass(type):
+    def __new__(cls, clsname, bases, attrs):
+        uppercase_attrs = {
+            attr if attr.startswith("__") else attr.upper(): v
+            for attr, v in attrs.items()
+        }
+
+        # Python 2 requires passing arguments to super:
+        return super(UpperAttrMetaclass, cls).__new__(
+            cls, clsname, bases, uppercase_attrs)
+
+        # Python 3 can use no-arg super() which infers them:
+        return super().__new__(cls, clsname, bases, uppercase_attrs)
+
+ +

Oh, and in Python 3 if you do this call with keyword arguments, like this:

+ +
class Foo(object, metaclass=MyMetaclass, kwarg1=value1):
+    ...
+
+ +

It translates to this in the metaclass to use it:

+ +
class MyMetaclass(type):
+    def __new__(cls, clsname, bases, dct, kwargs1=default):
+        ...
+
+ +

That’s it. There is really nothing more about metaclasses.

+ +

The reason behind the complexity of the code using metaclasses is not because of metaclasses, it’s because you usually use metaclasses to do twisted stuff relying on introspection, manipulating inheritance, vars such as __dict__, etc.

+ +

Indeed, metaclasses are especially useful to do black magic, and therefore complicated stuff. But by themselves, they are simple:

+ +
    +
  • intercept a class creation
  • +
  • modify the class
  • +
  • return the modified class
  • +
+ +

Why would you use metaclasses classes instead of functions?

+ +

Since __metaclass__ can accept any callable, why would you use a class since it’s obviously more complicated?

+ +

There are several reasons to do so:

+ +
    +
  • The intention is clear. When you read UpperAttrMetaclass(type), you know what’s going to follow
  • +
  • You can use OOP. Metaclass can inherit from metaclass, override parent methods. Metaclasses can even use metaclasses.
  • +
  • Subclasses of a class will be instances of its metaclass if you specified a metaclass-class, but not with a metaclass-function.
  • +
  • You can structure your code better. You never use metaclasses for something as trivial as the above example. It’s usually for something complicated. Having the ability to make several methods and group them in one class is very useful to make the code easier to read.
  • +
  • You can hook on __new__, __init__ and __call__. Which will allow you to do different stuff, Even if usually you can do it all in __new__, some people are just more comfortable using __init__.
  • +
  • These are called metaclasses, damn it! It must mean something!
  • +
+ +

Why would you use metaclasses?

+ +

Now the big question. Why would you use some obscure error-prone feature?

+ +

Well, usually you don’t:

+ +
+

Metaclasses are deeper magic that 99% of users should never worry about it. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why).

+ +

Python Guru Tim Peters

+
+ +

The main use case for a metaclass is creating an API. A typical example of this is the Django ORM. It allows you to define something like this:

+ +
class Person(models.Model):
+    name = models.CharField(max_length=30)
+    age = models.IntegerField()
+
+ +

But if you do this:

+ +
person = Person(name='bob', age='35')
+print(person.age)
+
+ +

It won’t return an IntegerField object. It will return an int, and can even take it directly from the database.

+ +

This is possible because models.Model defines __metaclass__ and it uses some magic that will turn the Person you just defined with simple statements into a complex hook to a database field.

+ +

Django makes something complex look simple by exposing a simple API and using metaclasses, recreating code from this API to do the real job behind the scenes.

+ +

The last word

+ +

First, you know that classes are objects that can create instances.

+ +

Well, in fact, classes are themselves instances. Of metaclasses.

+ +
>>> class Foo(object): pass
+>>> id(Foo)
+142630324
+
+ +

Everything is an object in Python, and they are all either instance of classes or instances of metaclasses.

+ +

Except for type.

+ +

type is actually its own metaclass. This is not something you could reproduce in pure Python, and is done by cheating a little bit at the implementation level.

+ +

Secondly, metaclasses are complicated. You may not want to use them for very simple class alterations. You can change classes by using two different techniques:

+ + + +

99% of the time you need class alteration, you are better off using these.

+ +

But 98% of the time, you don’t need class alteration at all.

+
+
    +
  1. + +

    +
  2. +
+
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2023/06/29/monkey-patching-in-go.html b/2023/06/29/monkey-patching-in-go.html new file mode 100644 index 0000000..ffb66a1 --- /dev/null +++ b/2023/06/29/monkey-patching-in-go.html @@ -0,0 +1,1543 @@ + + + +Monkey Patching in Go - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

Monkey Patching in Go

  + +
+
+ + +
+

本文是 Monkey Patching in Go 的阅读理解.

+
+ +

原文 Monkey Patching in Go

+ +

Many people think that monkey patching is something that is restricted to dynamic languages like Ruby and Python. That is not true however, as computers are just dumb machines and we can always make them do what we want! Let’s look at how Go functions work and how we can modify them at runtime. This article will use a lot of Intel assembly syntax, so I’m assuming you can read it already or are using a reference while reading.

+ + + +

If you’re not interested in how it works and you just want to do monkey patching, then you can find the library here.

+ +

Let’s look at what the following code produces when disassembled:

+ +
package main
+
+func axx() int {
+    return 1
+}
+
+func main() {
+    print(axx())
+}
+
+ +
+

Samples should be built with go build -gcflags='-N -l' to disable inlining. For this article I assume your architecture is 64-bits and that you’re using a unix-based operating system like Mac OSX or a Linux variant. +编译参数说明可以参考: Go gcflags/ldflags 的说明

+
+ +

When compiled and looked at through Hopper, the above code will produce this assembly code:

+ +

I will be referring to the addresses of the various instructions displayed on the left side of the screen.

+ +

+ +

Our code starts in procedure main.main, where instructions 0x2010 to 0x2026 set up the stack. You can read more about that here, I will be ignoring that code for the rest of the article.

+ +

Line 0x202a is the call to function main.a at line 0x2000 which simply moves 0x1 onto the stack and returns. Lines 0x202f to 0x2037 then pass that value on to runtime.printint.

+ +

Simple enough! Now let’s take a look at how function values are implemented in Go.

+ +

How function values work in Go

+ +

Consider the following code:

+ +
package main
+
+import (
+	"fmt"
+	"unsafe"
+)
+
+func a() int { return 1 }
+
+func main() {
+	f := a
+	fmt.Printf("0x%x\n", *(*uintptr)(unsafe.Pointer(&f)))
+}
+
+ +
+

Go 世界中的函数是一等公民

+
+ +

What I’m doing on line 11 is assigning a to f, which means that doing f() will now call a. Then I use the unsafe Go package to directly read out the value stored in f. If you come from a C background you might expect f to simply be a function pointer to a and thus this code to print out 0x2000 (the location of main.a as we saw above). When I run this on my machine I get 0x102c38, which is an address not even close to our code! When disassembled, this is what happens on line 11 above:

+ +

+ +

This references something called main.a.f, and when we look at that location, we see this:

+ +

+ +

Aha! main.a.f is at 0x102c38 and contains 0x2000, which is the location of main.a. It seems f isn’t a pointer to a function, but a pointer to a pointer to a function. Let’s modify the code to compensate for that.

+ +
package main
+
+import (
+	"fmt"
+	"unsafe"
+)
+
+func a() int { return 1 }
+
+func main() {
+	f := a
+	fmt.Printf("0x%x\n", **(**uintptr)(unsafe.Pointer(&f)))
+}
+
+ +
+

胖指针!!!

+
+ +

This will now print 0x2000, as expected. We can find a clue as to why this is implemented as it is here. Go function values can contain extra information, which is how closures and bound instance methods are implemented.

+ +

Let’s look at how calling a function value works. I’ll change the code to call f after assigning it.

+ +
package main
+
+func a() int { return 1 }
+
+func main() {
+	f := a
+	f()
+}
+
+ +

When we disassemble this we get the following:

+ +

+ +

main.a.f gets loaded into rdx, then whatever rdx points at gets loaded into rbx, which then gets called. The address of the function value always gets loaded into rdx, which the code being called can use to load any extra information it might need. This extra information is a pointer to the instance for a bound instance method and the closure for an anonymous function. I advise you to take out a disassembler and dive deeper if you want to know more!

+ +

Let’s use our newly gained knowledge to implement monkeypatching in Go.

+ +

Replacing a function at runtime

+ +

What we want to achieve is to have the following code print out 2:

+ +
package main
+
+func a() int { return 1 }
+func b() int { return 2 }
+
+func main() {
+	replace(a, b)
+	print(a())
+}
+
+ +

Now how do we implement replace? We need to modify function a to jump to b’s code instead of executing its own body. Essentialy, we need to replace it with this, which loads the function value of b into rdx and then jumps to the location pointed to by rdx.

+ +
mov rdx, main.b.f ; 48 C7 C2 ?? ?? ?? ??
+jmp [rdx] ; FF 22
+
+ +

I’ve put the corresponding machine code that those lines generate when assembled next to it (you can easily play around with assembly using an online assembler like this). Writing a function that will generate this code is now straightforward, and looks like this:

+ +
+

关于这段汇编的解释, 实际上这是作者使用了两个汇编指令(就是上面的 movjmp).

+ +

假如说我们想把 a() 调用替换为 b(), 而且 b 函数在生成的可执行文件中的内存位置为 0x01020304, 那么, 需要做的就是生成下面这样的机器码(后面是对应的汇编指令):

+ +
9:	48 c7 c2 04 03 02 01 	mov    $0x01020304,%rdx
+10:	ff 22                	jmpq   *(%rdx)
+
+ +

只需要把这段机器码替换到原始函数的机器码开始位置, 就能实现调用 a 的时候跳转到 b. 从原理上说, 我们还是调用的 a 函数, 不过在 a 函数的开头现在有个跳转指令, 直接跳转到了 b 函数的位置执行, 执行完成之后由 b 函数里面的 ret 指令直接返回到 a 的调用者的下一条指令继续执行. 函数参数和返回值是依赖栈帧传递的, 这里替换之后不会影响栈, 因此也不会影响参数和返回值.

+ +

assembleJump 函数就是用来生成这种机器码的.

+
+ +
import "unsafe"
+
+func assembleJump(f func() int) []byte {
+	funcVal := *(*uintptr)(unsafe.Pointer(&f))
+	return []byte{
+		0x48, 0xC7, 0xC2,
+		byte(funcval >> 0),
+		byte(funcval >> 8),
+		byte(funcval >> 16),
+		byte(funcval >> 24), // MOV rdx, funcVal
+		0xFF, 0x22,          // JMP [rdx]
+	}
+}
+
+ +

We now have everything we need to replace a’s function body with a jump to b! The following code attempts to copy the machine code directly to the location of the function body.

+ +
package main
+
+import (
+	"syscall"
+	"unsafe"
+)
+
+func a() int { return 1 }
+func b() int { return 2 }
+
+// 这里获取运行时代码段中 `b` 代表的函数指针的内存区域
+// 通过覆写这块区域, 就可以实现函数指针指向偷天换日的效果
+func rawMemoryAccess(b uintptr) []byte {
+	return (*(*[0xFF]byte)(unsafe.Pointer(b)))[:]
+}
+
+func assembleJump(f func() int) []byte {
+	funcVal := *(*uintptr)(unsafe.Pointer(&f))
+	return []byte{
+		0x48, 0xC7, 0xC2,
+		byte(funcVal >> 0),
+		byte(funcVal >> 8),
+		byte(funcVal >> 16),
+		byte(funcVal >> 24), // MOV rdx, funcVal
+		0xFF, 0x22, // JMP [rdx]
+	}
+}
+
+func replace(orig, replacement func() int) {
+	bytes := assembleJump(replacement)
+	functionLocation := **(**uintptr)(unsafe.Pointer(&orig))
+	window := rawMemoryAccess(functionLocation)
+	copy(window, bytes)
+}
+
+func main() {
+	replace(a, b)
+	print(a())
+}
+
+ +

Running this code does not work however, and will result in a segmentation fault. This is because the loaded binary is not writable by default. We can use the mprotect syscall to disable this protection, and this final version of the code does exactly that, resulting in function a being replaced by function b, and 2 being printed.

+ +
package main
+
+import (
+	"syscall"
+	"unsafe"
+)
+
+func a() int { return 1 }
+func b() int { return 2 }
+
+func getPage(p uintptr) []byte {
+	return (*(*[0xFFFFFF]byte)(unsafe.Pointer(p & ^uintptr(syscall.Getpagesize()-1))))[:syscall.Getpagesize()] // 拷贝一页 16Kb
+}
+
+func rawMemoryAccess(b uintptr) []byte {
+	return (*(*[0xFF]byte)(unsafe.Pointer(b)))[:] // 拷贝 255 个内存单元出去, 实际上只需要操作 9 个, 这里拷贝的数量大于 9 个就行
+}
+
+func assembleJump(f func() int) []byte {
+	funcVal := *(*uintptr)(unsafe.Pointer(&f))
+	return []byte{
+		0x48, 0xC7, 0xC2,
+		byte(funcVal >> 0),
+		byte(funcVal >> 8),
+		byte(funcVal >> 16),
+		byte(funcVal >> 24), // MOV rdx, funcVal
+		0xFF, 0x22, // JMP rdx
+	}
+}
+
+func replace(orig, replacement func() int) {
+	bytes := assembleJump(replacement)
+	functionLocation := **(**uintptr)(unsafe.Pointer(&orig))
+	window := rawMemoryAccess(functionLocation)
+	page := getPage(functionLocation)
+	syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC)
+	copy(window, bytes)
+}
+
+func main() {
+	replace(a, b)
+	print(a())
+}
+
+ +

Wrapping it up in a nice library

+ +

I took the above code and put it in an easy to use library. It supports 32 bit, reversing patches, and patching instance methods. I wrote a couple of examples and put those in the README.

+ +

Conclusion

+ +

Where there’s a will there’s a way! It’s possible for a program to modify itself at runtime, which allows us to implement cool tricks like monkey patching.

+ +

I hope you got something useful out of this blogpost, I know I had fun making it!

+ +

个人补充

+ +
+

这块我在研究上面的时候的实验尝试, 无论理解/不理解 Go 的汇编相关的东西, 对上文的阅读是没有影响的, 因此这部分 不用看

+
+ +

本文中用到的编译/汇编, 使用下面的指令完成:

+ +

编译为目标文件

+ +
> go tool compile -N -l main.go
+
+ +

目标文件反汇编

+ +
> go tool objdump -gnu main.o
+
+ +
TEXT %22%22.axx(SB) gofile../root/xxx/main.go
+  main.go:3		0x428			48c744240800000000	MOVQ $0x0, 0x8(SP)                   // movq $0x0,0x8(%rsp)
+  main.go:4		0x431			48c744240801000000	MOVQ $0x1, 0x8(SP)                   // movq $0x1,0x8(%rsp)
+  main.go:4		0x43a			c3			        RET                                  // retq
+
+TEXT %22%22.main(SB) gofile../root/xxx/main.go
+  main.go:7		0x43b			64488b0c2500000000	MOVQ FS:0, CX                    // mov %fs:,%rcx		[5:9]R_TLS_LE
+  main.go:7		0x444			483b6110		CMPQ 0x10(CX), SP                    // cmp 0x10(%rcx),%rsp
+  main.go:7		0x448			7645			JBE 0x48f                            // jbe 0x48f
+  main.go:7		0x44a			4883ec18		SUBQ $0x18, SP                       // sub $0x18,%rsp
+  main.go:7		0x44e			48896c2410		MOVQ BP, 0x10(SP)                    // mov %rbp,0x10(%rsp)
+  main.go:7		0x453			488d6c2410		LEAQ 0x10(SP), BP                    // lea 0x10(%rsp),%rbp
+  main.go:8		0x458			0f1f00			NOPL 0(AX)                           // nopl (%rax)
+  main.go:8		0x45b			e800000000		CALL 0x460                           // callq 0x460		[1:5]R_CALL:"".axx
+  main.go:8		0x460			488b0424		MOVQ 0(SP), AX                       // mov (%rsp),%rax
+  main.go:8		0x464			4889442408		MOVQ AX, 0x8(SP)                     // mov %rax,0x8(%rsp)
+  main.go:8		0x469			e800000000		CALL 0x46e                           // callq 0x46e		[1:5]R_CALL:runtime.printlock
+  main.go:8		0x46e			488b442408		MOVQ 0x8(SP), AX                     // mov 0x8(%rsp),%rax
+  main.go:8		0x473			48890424		MOVQ AX, 0(SP)                       // mov %rax,(%rsp)
+  main.go:8		0x477			0f1f4000		NOPL 0(AX)                           // nopl (%rax)
+  main.go:8		0x47b			e800000000		CALL 0x480                           // callq 0x480		[1:5]R_CALL:runtime.printint
+  main.go:8		0x480			e800000000		CALL 0x485                           // callq 0x485		[1:5]R_CALL:runtime.printunlock
+  main.go:9		0x485			488b6c2410		MOVQ 0x10(SP), BP                    // mov 0x10(%rsp),%rbp
+  main.go:9		0x48a			4883c418		ADDQ $0x18, SP                       // add $0x18,%rsp
+  main.go:9		0x48e			c3			    RET                                  // retq
+  main.go:7		0x48f			e800000000		CALL 0x494                           // callq 0x494		[1:5]R_CALL:runtime.morestack_noctxt
+  main.go:7		0x494			eba5			JMP %22%22.main(SB)                  // jmp 0x43b
+
+ +

对上面汇编的一点解释:

+ +
+

预备知识:

+ +
    +
  1. 线程本地存储 TLS
  2. +
  3. GO 语言的运行时初始化过程解析
  4. +
+
+ +
    +
  1. 0x43b ~ 0x448 是 Go 进入函数执行之前的初始化工作, 其实就是设置 TLS 以及运行时栈大小检查/分配
  2. +
  3. 0x44a ~ 0x453 是为 main 函数设置运行时栈, 栈空间大小设置为 0x10, 栈低指针被设置为 0x10(%rsp), 也就是 rsp 再加上 10 的位置, 栈顶指针被设置为 0x18(%rsp) 位置.
  4. +
  5. 0x458 nopl 是一个无意义的指令, 纯粹是为了占位置. 如果函数有参数的话, 这里会是设置 axx 参数相关代码, axx 收到的参数实际上.
  6. +
  7. 0x45b 发起对函数 axx 的调用
  8. +
+ +

栈设置的具体解释(注意栈是往下生长的):

+ +
; 开辟栈空间, 操作前的栈顶记为 SP0, 栈顶指针现在是 SP0 - 0x18, 记为 SP1
+; 此时 SP0-SP1 之间有 0x18/8 = 3  64 位存储单元: (高地址, sp0) [X, X, X] (低地址, sp1)
+sub $0x18,%rsp
+
+; 把原来的栈底记作 BP0, 这一步将它保存在 `0x10(%rsp)`
+; 操作完了之后栈内容变为 (高地址, sp0) [BP0, X, X] (低地址, sp1)
+mov %rbp,0x10(%rsp)
+
+; 设置新的栈低, 操作完之后, 栈内容不变, 只是 BP 寄存器发生了变化
+lea 0x10(%rsp),%rbp
+
+; 剩下的那两个栈存储区域,  main 函数的其他地方用到了, 可以看上面完整的汇编
+
+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2023/09/15/time-sequence-trending-algorithm.html b/2023/09/15/time-sequence-trending-algorithm.html new file mode 100644 index 0000000..fff15de --- /dev/null +++ b/2023/09/15/time-sequence-trending-algorithm.html @@ -0,0 +1,1511 @@ + + + +常见的时间序列趋势判别算法 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

常见的时间序列趋势判别算法

  + +
+
+ + +

本文介绍常见的时间序列趋势判别算法:

+ +
    +
  1. 多项式拟合(斜率)
  2. +
  3. Mann-Kendall 趋势检验检验
  4. +
  5. Cox-stuart 趋势检验
  6. +
+ +

多项式拟合(最小二乘法)

+ +

基本原理

+ +

核心是使用最小二乘法见序列拟合成一条直线, 然后根据直线的斜率 k 判断序列的走势. 如果返回的是正数则正增长, 如果返回的是负数则为下降, 如果为 0 则表示没有趋势.

+ + + +

本方法的优点是方法简单, 可解释性强. 缺点是要求趋势是线性的, 当数去波动较大时, 无法准确拟合.

+ +
def trendline(data):
+    order = 1
+    index = [i for i in range(1, len(data) + 1)]
+    coeffs = np.polyfit(index, list(data), order)
+    slope = coeffs[-2]
+    return float(slope)
+
+
+resultent = trendline(List)
+print(resultent)
+
+ +

该方法主要用到的函数是 np.polyfit(x, y, deg, *args), 其中:

+ +
    +
  1. deg 为需要拟合函数的最高次数, 当 deg = 0 时, 式子是一个常数项, 即 $y = a_0$
  2. +
  3. np.polyfit 函数的返回值是拟合好之后的参数, 即 $a_n, a_{n-1}, …, a_0$
  4. +
+ +

np.polyfit 的返回结果可以传递给 np.ploy1d, 这个函数会生成对应的多项式表达式.

+ +

+ +

实验

+ +
import numpy as np
+from matplotlib import pyplot as plt
+
+def func(x):
+    '''原函数'''
+    return 2*x*4 - 2*x**3 - 5*x**2 - x + 1
+
+def trendline(x, y, n):
+    '''拟合函数,输出参数'''
+    model = np.polyfit(x, y, deg=n)
+    return np.poly1d(model)
+
+
+# 生成 30 个时序坐标点 (x, y),前 20 个点用于拟合,后 10 个点用于预测
+x = np.linspace(-3, 3, 30)
+y = func(x)
+y1 = y + np.random.randn(30) * 1.5 # 加上噪声
+
+ff = trendline(x[:20], y1[:20], n=4)
+pred = np.poly1d(ff)(x)
+
+# 作图
+plt.figure(figsize=(15, 7))
+plt.scatter(x, y, color='blue', label="raw")
+plt.plot(x, y1, color='yellow', label='real')
+plt.plot(x, pred, color='red', label='fit')
+plt.legend()
+plt.show()
+
+ +

运行结果如下:

+ +

Cox-stuart 趋势检验

+ +

基本原理

+ +

Cox-Stuart 趋势检验过程直接考虑数据的变化趋势, 若数据有上升趋势, 那么排在后面的数据的值要比排在前面的数据的值显著的大, 反之若数据有下降趋势, 那么排在后面的数据的值要比排在前面的数据的值显著的小, 利用前后两个时期不同数据的差值正负来判断数据总的变化趋势.

+ +

算法过程

+ +

假设 $n$ 个数据形成数据列 $X = [x_1, x_2, …, x_n]$, +取 $x_i$ 和 $x_{i+c}$ 组成一对 $(x_i, x_{i+c})$, 这里如果 $n$ 为偶数,则 $c=\frac{n}{2}$ ,如果 $n$ 是奇数,则 $c=\frac{(n+1)}{2}$, 当 $n$ 为偶数时,数据对共有 $n’=c$ 对, 而 $n$ 是奇数时, 共有 $n’=c-1$ 对.

+ +

用每一对的两元素差 $D_i = x_i − x_{i+c}$ 的符号来衡量增减, 令 $S+$ 为正的 $D_i$ 的数目, $S-$ 为负的 $D_i$ 的数目. 显然当正号太多时有下降趋势, 反之有增长趋势. 在没有趋势的零假设下他们因服从二项分布 $B(n’, 0.5)$.

+ +

用 $p(+)$ 表示取到正数的概率, 用 $p(-)$ 表示取到负数的概率, 这样我们就得到符号检验方法来检验序列是否存在趋势性.

+ +

本方法优点是可以不依赖趋势结构, 快速判断出趋势是否存在. 缺点是未考虑数据的时序性, 仅仅只能通过符号检验来判断.

+ +

实验:

+ +
import scipy.stats as stats
+
+
+def cos_stuart(x):
+    n = len(x)
+    xx = x  # 因为需要删除,所以复制一份
+    if n % 2 == 1:
+        del xx[n // 2]
+    c = n // 2
+
+    # 计算正负符号的数量
+    n_pos = n_neg = 0  # n_pos=S+  n_neg=S-
+    for i in range(c):
+        diff = xx[i + c] - x[i]
+        if diff > 0:
+            n_pos += 1
+        elif diff < 0:
+            n_neg += 1
+        else:
+            continue
+
+    num = n_pos + n_neg
+    k = min(n_pos, n_neg)  # 求K值
+    p_value = 2 * stats.binom.cdf(k, num, 0.5)  # 计算p_value
+    print("fall:{}, rise:{}, p-value:{}".format(n_neg, n_pos, p_value))
+
+    # p_value<0.05,零假设不成立
+    if n_pos > n_neg and p_value < 0.05:
+        return "increasing"
+    elif n_neg > n_pos and p_value < 0.05:
+        return "decreasing"
+    else:
+        return "no trend"
+
+x = list(range(1, 20))
+cos_stuart(x)
+
+#>>> 输出 <<<
+#> fall:0, rise:9, p-value:0.00390625
+#> 'increasing'
+
+ +

Mann-Kendall 趋势检验

+ +

基本原理

+ +

使用 MK 算法检验时序数据大致趋势, 趋势分为无明显趋势(稳定), 趋势上升, 趋势下降.

+ +

MK 检验的基础:

+ +
    +
  • 当没有趋势时, 随时间获得的数据是独立同分布的, 数据随着时间不是连续相关的.
  • +
  • 所获得的时间序列上的数据代表了采样时的真实条件, 样本要具有代表性.
  • +
  • MK 检验不要求数据是正态分布, 也不要求变化趋势是线性的.
  • +
  • 如果有缺失值或者值低于一个或多个检测限制, 是可以计算 MK 检测的, 但检测性能会受到不利影响.
  • +
  • 独立性假设要求样本之间的时间足够大, 这样在不同时间收集的测量值之间不存在相关性
  • +
+ +

算法过程(没看懂)

+ +

第一部分, 计算趋势

+ +
    +
  1. 设零假设 $H_0$ 没有单调趋势, $H_a$ 有单调趋势
  2. +
  3. 数据按照采集时间一次取出, 记为 $X = [x_1, x_2, x_3, …, x_n]$
  4. +
  5. +

    确定所有 $C_n^2$ 个 $x_j - x_i$ 的差值函数 $Sign(x_j - x_i)$, $1 \le j \lt i \le n$, 函数定义如下:

    + +\[Sign(x_j - x_i) = \begin{cases} + -1, & x_j - x_i \lt 0 \\ + 0, & x_j - x_i = 0, 或者数据缺失 \\ + 1, & x_j - x_i \gt 0 \\ +\end{cases}\] +
  6. +
  7. +

    求检验统计量

    + +\[S = \sum_{i=1}^{n-1} \sum_{j=i+1}^{n} Sign(x_j - x_i)\] + +

    如果 $S$ 是一个正数, 那么后一部分的观测值相比之前的观测值会趋向于变大, 如果 $S$ 是一个负数, 那么后一部分的观测值相比之前的观测值会趋向于变小.

    +
  8. +
  9. +

    如果 $n$ 比较小(比如小于 8), 依据 Gilbert (1987, page 209, Section 16.4.1)中所描述的程序, 要去概率表(Gilbert 1987, Table A18, page 272)里面查找 $S$, 这个表目前找不到了, 这里给出一个 $S$ 和 $Z$ 的关系式:

    + +\[Z = \frac{S}{\frac{n(n-1)}{2}}\] + +

    如果此概率小于 $\alpha$ (认为没有趋势时的截止概率), 那就拒绝零假设, 认为趋势存在. +如果在概率表中找不到 $n$ (存在结数据(tied data values)会发生此情况), 就用表中远离 $0$ 的下一个值, 比如如果概率表中没有 $S=12$, 那么就用 $S=13$ 来处理也是一样的.

    + +

    +
  10. +
  11. +

    当 $n \ge 8$ 时, 按照 Gilbert (1987, page 211, Section 16.4.2)中的程序, 统计量 $S$ 大致的服从正态分布, 方差通过以下方式计算

    + +

    如果数据中, 每个数字都是唯一的, 则方差

    + +\[Var(S)= \frac{n(n-1)(2n+5)}{18}\] + +

    如果数据中数据存在不唯一, 则公式变为

    + +\[Var(S)= \frac{n(n-1)(2n+5) - \sum_{p=1}^{g} t_p(t_p-1)(2t_p+5)}{18}\] + +

    其中, $p$ 为重复数据数量, $g$ 为唯一数数量(结组数), $t_p$ 为每个重复数重复的次数.

    + +

    当因为有或者未检测到而出现结时, $Var(S)$ 可以通过 Helsel(2005, p.191)中的结修正方法来修正.

    +
  12. +
  13. +

    计算标准化后的检验统计量 Z 如下:

    + +\[Z_{MK} = \begin{cases} + \frac{S-1}{\sqrt{V_{ar}(S)}}, & S>0 \\ + 0, & S=0 \\ + \frac{S+1}{\sqrt{V_{ar}(S)}}, & S<0 \\ +\end{cases}\] +
  14. +
  15. +

    回到 $H_0$ 和 $H_a$ 假设

    + +
    +

    什么是一型错误和二型错误

    + +
      +
    • 一型错误: 原假设是正确的, 推翻了原假设造成的错误
    • +
    • 二型错误: 原假设是错误的, 没有推翻原假设造成的错误
    • +
    +
    + +

    其一型错误率为 $\alpha$, $0 \lt \alpha \lt 0.5$(注意 $\alpha$ 是 MK 检验错误地拒绝了零假设时可容忍的概率, 即 MK 检验拒绝了零假设是错误的, 但这个事情发生概率是 $\alpha$, 我们可以容忍这个错误).

    + +
      +
    1. +

      针对 $H_0$ 没有单调趋势, $H_a$ 有单调增趋势

      + +

      如果 $Z_{MK} \ge Z_{1 - \alpha}$, 就拒绝零假设 $H_0$, 接受替代假设 $H_a$. 其中 $Z_{1 - \alpha}$ 是标准正态分布的 $100(1-\alpha)^{th}$ 百分位(percentile, 不是很懂他说的这些是什么, 需要看一下参考文献). 这些百分位在许多统计书(比如 Gilbert 1987, Table A1, page 254)和统计软件包中都有提供.

      +
    2. +
    3. +

      针对 $H_0$ 没有单调趋势, $H_a$ 有单调减趋势

      + +

      如果 $Z_{MK} \le - Z_{1 - \alpha}$, 就拒绝零假设 $H_0$, 接受替代假设 $H_a$.

      +
    4. +
    5. +

      针对 $H_0$ 没有单调趋势, $H_a$ 有单调递增或递减趋势

      + +

      如果 $\lvert Z_{MK} \rvert \ge Z_{1 - \frac{\alpha}{2}} $, 就拒绝零假设 $H_0$, 接受替代假设 $H_a$.

      +
    6. +
    +
  16. +
  17. +

    最终得到错误率为 $\alpha$, $0 \lt \alpha \lt 0.5$, $\lvert Z_{MK} \rvert \ge Z_{1-\frac{\alpha}{2}}$, $Z_{1-\frac{\alpha}{2}}$ 为 $ppf(1-\frac{\alpha}{2})$(为什么是 $1 - \frac{\alpha}{2}$, 是因为概率为正态分布而且左右两边均存在, 且 $1-\frac{\alpha}{2}$ 为置信度)

    + +

    在双边趋势检验中, 对于给定的置信水平 $\alpha$ (显著性水平), 若 $\lvert Z \rvert \ge Z_{1 - \frac{\alpha}{2}}$, 则原假设 $H_0$ 是不可接受的, 即在置信水平 $\alpha$ (显著性检验水平)上, 时间序列数据存在明显的上升或下降趋势. $Z$ 为正值表示上升趋势, 负值表示减少趋势, $Z$ 的绝对值在大于等于 $1.645, 1.96, 2.576$ 时表示分别通过了置信度 $90\%, 95\%, 99\%$ 的显著性检验.

    + +

    计算过程: 以 $ \alpha = 0.1$ 为例, $Z_{1-\frac{\alpha}{2}} = Z_{0.95}$, 查询标准正态分布表 $Z_{0.95} = 1.645$, 故 $Z \ge 1.645$ 时通过 $90\%$ 的显著性检验, $H_0$ 假设不成立, $Z \gt 0$, 序列存在上升趋势.

    +
  18. +
  19. +

    衡量趋势大小的指标, 用倾斜度 $\beta$ 表示为:

    + +\[\beta = median(\frac{x_j - x_i}{j - i}) \quad \forall \space 1 \lt i \lt j \lt n\] +
  20. +
  21. +

    $P$ 值计算验证(可选)

    + +\[P = 2(1-cdf(\lvert Z_{MK} \rvert))\] +
  22. +
+ +

第二部分, 查找突变点

+ +
    +
  1. +

    设 $S_k$ 表示 $X$ 中的第 $j$ 个样本 $x_j \gt x_i (1 \le i \le j)$ 的累计数, 定义统计量 $S_k$:

    + +\[S_k = \sum_{j=1}^k r_j \quad (r_j = \begin{cases} + 1 & x_j \gt x_i \\ + 0 & x_j \le x_i \\ +\end{cases}, \quad i=1,2,...,j;k=1,2,....n)\] +
  2. +
  3. +

    在时间序列随机独立的假定下, $S_k$ 的均值为:

    + +\[E[S_k] = \frac{k(k-1)}{4}\] + +

    方差为

    + +\[Var[S_k] = \frac{k(k-1)(2k+5)}{72} \quad 1 \le k \le n\] +
  4. +
  5. +

    将 $S_k$ 标准化:

    + +

    其中 $UF_1=0$, 给定显著性水平 $\alpha$, 若 $\lvert UF_k \rvert \gt U_{\alpha}$, 则表明序列存在明显的趋势变化. 所有 $UF_k$ 可组成一条曲线, 将此方法引用到反序列, 将反序列 $x_n, x_{n-1}, …, x_1$ 表示为 $x’_1, x’_2, …, x’_n$. $j$ 表示第 $j$ 个样本 $x_j$ 大于 $x_i (j \le i \le n)$ 的累计数. 当 $j’ = n+1-j$ 时, $j = r’j$, 则反序列的 $UB_k$ 为:

    + +\[UB_k = -UF_k \quad k'=n+1-j \quad j,j'=1,2,...,n...\] + +

    其中 $UB_1=0$, $UB_k$ 不是简单的等于 $UF_k$ 负值, 而是进行了倒置再取负, 此处 $UF_k$ 是根据反序列算出来的.

    + +

    给定显著性水平, 若 $\alpha =0.05$, 那么临界值为 $\pm 1.96$, 绘制 $UF_k$ 和 $UB_k$ 曲线图和 $\pm 1.96$ 俩条直线再一张图上, 若 $UF_k$ 得值大于 $0$, 则表明序列呈现上升趋势, 小于 $0$ 则表明呈现下降趋势, 当它们超过临界直线时, 表明上升或下降趋势显著. 超过临界线的范围确定为出现突变的时间区域. 如果 $UF_k$ 和 $UB_k$ 两条曲线出现交点, 且交点在临界线内, 那么交点对应的时刻便是突变开始的时间.

    +
  6. +
+ +

优点: 功能强大, 不需要样本遵从一定的分布, 部分数据缺失不会对结果造成影响, 不受少数异常值的干扰, 适用性强. 不但可以检验时间序列的变化趋势, 还可以检验时间序列是否发生了突变.

+ +

缺点: 暂未发现, 待后续补充.

+ +

主要参考资料:

+ + + +

更多资料:

+ + +
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2023/09/17/linear-regression-decision-tree-trending.html b/2023/09/17/linear-regression-decision-tree-trending.html new file mode 100644 index 0000000..54f46c0 --- /dev/null +++ b/2023/09/17/linear-regression-decision-tree-trending.html @@ -0,0 +1,1387 @@ + + + +用树回归方法画股票趋势线 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

用树回归方法画股票趋势线

  + +
+
+ + +

原文: 机器学习_用树回归方法画股票趋势线

+ +

本篇的主题是分段线性拟合, 也叫回归树, 是一种集成算法, 它同时使用了决策和线性回归的原理, 其中有两点不太容易理解, 一个是决策树中熵的概念, 一个是线性拟合时求参数的公式为什么是由矩阵乘法实现的. 如需详解, 请见前篇:

+ + + +

画出股票的趋势线

+ +

我们常在股票节目里看到这样的趋势线:

+ +

趋势线

+ + + +

比如说平台突破就可以买入了, 几千支股票, 能不能用程序的方式筛选哪支突破了呢? 需要解决的主要问题是: 怎么判断一段时间内股票的涨/跌/横盘, 以及一段趋势的起止点和角度呢?

+ +

分段线性拟合

+ +

这里我们使用分段线性拟合, 图中蓝色的点是某支股票每日的收盘价, 红色的直线为程序画出的趋势线. 稍做修改, 还可以轻松地画出每段趋势所在的箱体, 阻力线和支撑线, 以及判断此前一般时间的趋势. 下面我们就来看看原理和具体算法.

+ +

相关算法

+ +

线性回归

+ +

先看看线性回归(Linear regression), 线性回归是利用数理统计中回归分析, 来确定两种或两种以上变量间相互依赖的定量关系的一种统计分析方法. 简单地说, 二维中就是画一条直线, 让它离所有点都尽量地近(距离之和最小), 用线抽象地表达这些点. 具体请见《机器学习_最小二乘法,线性回归与逻辑回归》

+ +

线性回归

+ +

决策树

+ +

我们再看看决策树, 决策树(Decision Tree)决策树是一个预测模型: 它是通过一系列的判断达到决策的方法. 具体请见《机器学习_决策树与信息熵》.

+ +

决策树

+ +

树回归

+ +

树回归把决策树和线性回归集成在一起, 先决策树, 在每个叶节点上构建一个线性方程. 比如说数据的最佳拟合是一条折线, 那就把它切成几段用线性拟合, 每段切多长呢? 我们定义一个步长(以忽略小的波动, 更好地控制周期), 在整个区域上遍历, 找最合适的点(树的分叉点), 用该点切分成两段后, 分别线性拟合, 取整体误差和最小的点. 以此类拟, 再分到三段, 四段…, 为避免过拟合, 具体实现一般同时使用前剪枝和后剪枝.

+ +

代码

+ +
# -*- coding: utf-8 -*-
+
+import tushare as ts
+import pandas as pd
+import numpy as np
+import matplotlib.pyplot as plt
+
+
+# 用feature把dataSet按value分成两个子集
+def binSplitDataSet(dataSet, feature, value):
+    mat0 = dataSet[np.nonzero(dataSet[:, feature] > value)[0], :]
+    mat1 = dataSet[np.nonzero(dataSet[:, feature] <= value)[0], :]
+    return mat0, mat1
+
+
+# 求给定数据集的线性方程
+def linearSolve(dataSet):
+    m, n = np.shape(dataSet)
+    X = np.mat(np.ones((m, n)))
+    # 第一行补1,线性拟合要求
+    Y = np.mat(np.ones((m, 1)))
+    X[:, 1:n] = dataSet[:, 0 : n - 1]
+    Y = dataSet[:, -1]  # 数据最后一列是y
+    xTx = X.T * X
+    if np.linalg.det(xTx) == 0.0:
+        raise NameError(
+            "This matrix is singular, cannot do inverse,\n\
+        try increasing dur"
+        )
+    ws = xTx.I * (X.T * Y)  # 公式推导较难理解
+    return ws, X, Y
+
+
+# 求线性方程的参数
+def modelLeaf(dataSet):
+    ws, X, Y = linearSolve(dataSet)
+    return ws
+
+
+# 预测值和y的方差
+def modelErr(dataSet):
+    ws, X, Y = linearSolve(dataSet)
+    yHat = X * ws
+    return sum(np.power(Y - yHat, 2))
+
+
+def chooseBestSplit(dataSet, rate, dur):
+    # 判断所有样本是否为同一分类
+    if len(set(dataSet[:, -1].T.tolist()[0])) == 1:
+        return None, modelLeaf(dataSet)
+    m, n = np.shape(dataSet)
+    S = modelErr(dataSet)  # 整体误差
+    bestS = np.inf
+    bestIndex = 0
+    bestValue = 0
+    for featIndex in range(n - 1):  # 遍历所有特征, 此处只有一个
+        # 遍历特征中每种取值
+        for splitVal in set(dataSet[:, featIndex].T.tolist()[0]):
+            mat0, mat1 = binSplitDataSet(dataSet, featIndex, splitVal)
+            if (np.shape(mat0)[0] < dur) or (np.shape(mat1)[0] < dur):
+                continue  # 样本数太少, 前剪枝
+            newS = modelErr(mat0) + modelErr(mat1)  # 计算整体误差
+            if newS < bestS:
+                bestIndex = featIndex
+                bestValue = splitVal
+                bestS = newS
+    if (S - bestS) < rate:  # 如差误差下降得太少,则不切分
+        return None, modelLeaf(dataSet)
+    mat0, mat1 = binSplitDataSet(dataSet, bestIndex, bestValue)
+    return bestIndex, bestValue
+
+
+def isTree(obj):
+    return type(obj).__name__ == "dict"
+
+
+# 预测函数,数据乘模型,模型是斜率和截距的矩阵
+def modelTreeEval(model, inDat):
+    n = np.shape(inDat)[1]
+    X = np.mat(np.ones((1, n + 1)))
+    X[:, 1 : n + 1] = inDat
+    return float(X * model)
+
+
+# 预测函数
+def treeForeCast(tree, inData):
+    if not isTree(tree):
+        return modelTreeEval(tree, inData)
+    if inData[tree["spInd"]] > tree["spVal"]:
+        if isTree(tree["left"]):
+            return treeForeCast(tree["left"], inData)
+        else:
+            return modelTreeEval(tree["left"], inData)
+    else:
+        if isTree(tree["right"]):
+            return treeForeCast(tree["right"], inData)
+        else:
+            return modelTreeEval(tree["right"], inData)
+
+
+# 对测试数据集预测一系列结果, 用于做图
+def createForeCast(tree, testData):
+    m = len(testData)
+    yHat = np.mat(np.zeros((m, 1)))
+    for i in range(m):  # m是item个数
+        yHat[i, 0] = treeForeCast(tree, np.mat(testData[i]))
+    return yHat
+
+
+# 绘图
+def draw(dataSet, tree):
+    plt.scatter(dataSet[:, 0], dataSet[:, 1], s=5)  # 在图中以点画收盘价
+    yHat = createForeCast(tree, dataSet[:, 0])
+    plt.plot(dataSet[:, 0], yHat, linewidth=2.0, color="red")
+    plt.show()
+
+
+# 生成回归树, dataSet是数据, rate是误差下降, dur是叶节点的最小样本数
+def createTree(dataSet, rate, dur):
+    # 寻找最佳划分点, feat为切分点, val为值
+    feat, val = chooseBestSplit(dataSet, rate, dur)
+    if feat == None:
+        return val  # 不再可分
+    retTree = {}
+    retTree["spInd"] = feat
+    retTree["spVal"] = val
+    lSet, rSet = binSplitDataSet(dataSet, feat, val)  # 把数据切给左右两树
+    retTree["left"] = createTree(lSet, rate, dur)
+    retTree["right"] = createTree(rSet, rate, dur)
+    return retTree
+
+
+if __name__ == "__main__":
+    df = ts.get_k_data(code="002230", start="2017-01-01")  # 科大讯飞今年的股票数据
+    e = pd.DataFrame()
+    e["idx"] = df.index  # 用索引号保证顺序X轴
+    e["close"] = df["close"]  # 用收盘价作为分类标准Y轴, 以Y轴高低划分X成段,并分段拟合
+    arr = np.array(e)
+    tree = createTree(np.mat(arr), 100, 10)
+draw(arr, tree)
+
+ +

分析

+ +

算法的拟合度和复杂度是使用步长, 误差下降和最小样本数控制的, 计算的时间跨度(一月/一年)也影响着程序运行时间.

+ +

例程中计算的是 科大讯飞 近一年来的股价趋势, 计算用时约十秒左右(我的机器速度还可以).

+ +

要计算所有的股票, 也需要不少时间. 所以具体实现时, 一方面可以利用当前价格和移动均线的相对位置过滤掉一些股票, 另一方面, 也可以将计算结果存储下来, 以避免对之前数据的重复计算.

+
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2023/09/20/decison-tree.html b/2023/09/20/decison-tree.html new file mode 100644 index 0000000..ca4c772 --- /dev/null +++ b/2023/09/20/decison-tree.html @@ -0,0 +1,1488 @@ + + + +决策树算法 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

决策树算法

  + +
+
+ + +

算法原理

+ +

树模型

+ +

决策树根节点开始一步步走到叶子节点(决策), 所有的数据最终都会落到叶子节点, 利用决策树既可以做分类也可以做回归.

+ +

树的组成

+ +
    +
  • 根节点: 第一个选择点
  • +
  • 非叶子节点与分支: 中间过程
  • +
  • 叶子节点: 最终的决策结果
  • +
+ +

决策树的训练是从给定的训练集构造(从根节点开始选择特征, 如何进行特征切分)出来一棵树, 然后将测试数据根据构造出来的树模型从上到下去走一遍, 得到决策结果.

+ +

一旦构造好了决策树, 那么分类或者预测任务就很简单了, 只需跟着树走一遍决策流程就可以了, 那么难点就在于如何构造出来一颗树.

+ + + +

熵的作用

+ +

根节点的选择该用哪个特征呢? 我们的目标应该是经过根节点的决策之后尽可能好的切分数据(即决策后分类的效果更好), 然后再找除了根节点用到的特征之外的其他特征中切分效果最好的特征作为决策树的后续节点, 然后是第三好, 第四好…

+ +

所以我们需要一种衡量标准, 来计算通过不同特征进行分支选择后的分类 情况, 找出来最好的那个当成根节点.

+ +

+ +

熵是表示随机变量不确定性的度量, 它的定义如下:

+ +\[H(X) = -\sum_{i=1}^{n} p_{i}\log{p_{i}}\] + +

举例有 $A = \lbrace 1,1,1,1,1,1,1,1,2,2 \rbrace$, $B=\lbrace 1,2,3,4,5,6,7,8,9,1 \rbrace$ 两个集合, 显然 $A$ 集合的熵值要低,因为 $A$ 里面只有两种类别, 相对稳定一些. 而 $B$ 中类别太多了, 熵值就会大很多. (在分类任务中我们希望通过节点分支后数据类别的熵值大还是小呢?)

+ +

信息增益原理

+ +

集合中的信息越混乱, 得到的熵值也就越大. 如当 $p=0$ 或 $p=1$ 时, $H(p)=0$, 随机变量完全没有不确定性. 当 $p=0.5$ 时, $H(p)=1$, 此时随机变量的不确定性最大.

+ +

信息增益

+ +

指的是在经过特征 $X$ 的分类之后, 使得结果熵相对于分类操作之前的熵的减小值.

+ +

决策树构造实例

+ +

现在有一个表, 其中数据列举了人的幸福与否和年龄,工作,家庭,贷款情况的关系, 我们基于这张表构建一个决策树.

+ +

首先我们可以计算得到, 根节点的信息熵为(6 个 no, 9 个 yes):

+ +\[\begin{aligned} +H ~=~& - \frac{6}{15} \times \log{\frac{6}{15}} - \frac{9}{15} \times \log{\frac{9}{15}} \\ + \approx ~& 0.97095 +\end{aligned}\] + +

然后对 4 个特征逐一分析, 分别尝试用他们作为决策依据, 看一下决策后那个特征对应的信息增益最大, 就可以认定这个特征是最好的分类特征. 要注意的是, 用特征切分原始数据集之后, 切分结果对应的信息熵是一个加权平均熵.

+ +

我们以 F3-HOME 特征分类来讲解. 它可以把数据分类为 2 个集合(这个特征有两个特征值).

+ +

在特征值为 0 的情况下, 有 6 个 no 值, 3 个 yes 值. +在特征值为 1 的情况下, 有 0 个 no 值, 6 个 yes 值.

+ +\[\begin{aligned} +H_{3} ~=~& \frac{9}{15} \times (-\frac{6}{9} \times \log{\frac{6}{9}} - \frac{3}{9} \times \log{\frac{3}{9}}) + \frac{6}{15} \times (-\frac{0}{6} \times \log{\frac{0}{6}} - \frac{6}{6} \times \log{\frac{6}{6}}) \\ + \approx ~& 0.55097 +\end{aligned}\] + +

用同样的办法, 可以求出来其他切分方式的熵值:

+ +\[\begin{aligned} +H_1 \approx ~& 0.88794 \\ +H_2 \approx ~& 0.64730 \\ +H_4 \approx ~& 0.60796 +\end{aligned}\] + +

可以看到使用 F3-HOME 特征, 信息增益是最大的, 因此可以选择它作为根节点.

+ +

接下来对于各个特征子树的节点, 构建下一层的决策树时候就是在子数据集合基础上, 再找最优特征作为分类依据. 更深一层的节点也是一样的原理.

+ +
from typing import Union
+
+import math
+import json
+
+
+# 创建数据
+def createDataSet():
+    # 数据
+    dataSet = [
+        [0, 0, 0, 0, "no"],
+        [0, 0, 0, 1, "no"],
+        [0, 1, 0, 1, "yes"],
+        [0, 1, 1, 0, "yes"],
+        [0, 0, 0, 0, "no"],
+        [1, 0, 0, 0, "no"],
+        [1, 0, 0, 1, "no"],
+        [1, 1, 1, 1, "yes"],
+        [1, 0, 1, 2, "yes"],
+        [1, 0, 1, 2, "yes"],
+        [2, 0, 1, 2, "yes"],
+        [2, 0, 1, 1, "yes"],
+        [2, 1, 0, 1, "yes"],
+        [2, 1, 0, 2, "yes"],
+        [2, 0, 0, 0, "no"],
+    ]
+
+    # 列名
+    features = ["F1-AGE", "F2-WORK", "F3-HOME", "F4-LOAN"]
+    return dataSet, features
+
+
+def maxDataSetLabel(dsLabelList):
+    """找到 dsLabelList 中元素的众数并返回"""
+    counter = {}
+
+    for i in dsLabelList:
+        cnt = counter.get(i, 0)
+        cnt += 1
+        counter[i] = cnt
+
+    key = max(counter, key=counter.get)
+    return key
+
+
+def dataSetEntropy(dataSet) -> float:
+    """计算 dataSet 的熵值"""
+    counter = {}
+    for row in dataSet:
+        label = row[-1]
+        cnt = counter.get(label, 0) + 1
+        counter[label] = cnt
+
+    result = 0
+    total = len(dataSet)
+
+    for val in counter.values():
+        prob = val / total
+        result -= prob * math.log(prob, 2)
+
+    return result
+
+
+def weightSubDataSetEntropy(subDataSet, dataSetLen) -> float:
+    """计算按照特征拆分好的子数据集的加权熵"""
+    entropy = 0
+    dataSetLen = float(dataSetLen)
+    for _dataSet in subDataSet.values():
+        _entropy = dataSetEntropy(_dataSet)
+        entropy += _entropy * len(_dataSet) / dataSetLen
+
+    return entropy
+
+
+def chooseBestFeatureToSplit(dataSet):
+    """计算信息增益最大的特征
+
+    1. 计算分裂之前的熵值
+    2. 找到可以用于分裂的特征列表
+    3. 尝试使用 2 里面的每个特征分裂构造子树, 然后判断那个信息增益最大
+    4. 使用信息增益最大的那个特征, 构造分类节点数据
+    """
+    dsLen = len(dataSet)
+    numberFeatures = len(dataSet[0]) - 1  # number of features
+    curEntropy = dataSetEntropy(dataSet)  # initial entropy
+
+    bestGain = -1
+    bestFeatIndex = -1
+    for feat in range(numberFeatures):
+        subDataSet = subDataSetByFeature(dataSet, feat)
+        entropy = weightSubDataSetEntropy(subDataSet, dsLen)
+
+        gain = curEntropy - entropy
+        if gain > bestGain:
+            bestGain = gain
+            bestFeatIndex = feat
+
+    return bestFeatIndex
+
+
+def subDataSetByFeature(dataSet, bestFeatIndex):
+    """按照 bestFeatIndex 指示的特征, 将 dataSet 分类"""
+    result = {}
+
+    for row in dataSet:
+        val = row[bestFeatIndex]
+
+        valList = result.get(val, [])
+        valList.append(row[:bestFeatIndex] + row[bestFeatIndex + 1 :])
+        result[val] = valList
+
+    return result
+
+
+def createTreeNode(dataSet, labelList) -> Union[str, dict]:
+    """
+    创建决策树
+    :param dataSet: 数据集
+    :param features: 特征列表
+    :return: 决策树
+    """
+    # 当前数据集合中, 标签数据
+    dsLabelList = [i[-1] for i in dataSet]
+
+    # 已经分好类别, 节点不需要再分裂了
+    if len(set(dsLabelList)) <= 1:
+        return dsLabelList[0]
+
+    # label 虽然还没有完全分开, 但是已经没有特征可供拆分了, 统计众数, 作为标签返回
+    if len(labelList) <= 1:
+        return maxDataSetLabel(dsLabelList)
+
+    # 找到最优的特征用于分裂
+    bestFeatIndex = chooseBestFeatureToSplit(dataSet)
+    bestFeat = labelList[bestFeatIndex]
+
+    # 删除被选择的特征
+    del labelList[bestFeatIndex]
+
+    # 按照特征, 将数据分成不同的子集
+    subDataSet = subDataSetByFeature(dataSet, bestFeatIndex)
+
+    # 遍历每个特征值, 然后递归的构造子树
+    node = {}
+    for featVal, _dataSet in subDataSet.items():
+        _labelList = labelList.copy()
+        node[featVal] = createTreeNode(_dataSet, _labelList)
+
+    # 创建树
+    tree = {bestFeat: node}
+    return tree
+
+
+dataSet, features = createDataSet()
+
+tree = createTreeNode(dataSet, features)
+print(json.dumps(tree))
+
+ +

信息增益率与 gini 系数

+ +
决策树算法
+ID3
+信息增益(有什么问题呢?)
+
+问题:ID当做特征,熵值为0,不适合解决稀疏特征,种类非常多的。
+
+C4.5
+信息增益率(解决ID3问题,考虑自身熵)
+
+CART
+使用GINI系数来当做衡量标准
+
+GINI系数
+\large Gini(p)=\sum_{k=1}^{K}p_{k}(1-p_{k})=1-\sum_{k=1}^{K}p_{k}^{2}
+
+(和熵的衡量标准类似,计算方式不相同)
+
+连续值
+进行离散化。
+
+
+
+六、预剪枝方法
+决策树剪枝策略
+为什么要剪枝
+决策树过拟合风险很大,理论上可以完全分得开数据 (想象一下,如果树足够庞大,每个叶子节点不就一个数据了嘛)
+
+剪枝策略
+(预剪枝,后减枝)
+
+预剪枝
+边建立决策树过程中进行剪枝的操作(更实用)。
+
+限制深度,叶子节点个数。叶子节点样本数,信息增益量等。
+
+七、后剪枝方法
+后剪枝:当建立完决策树后来进行剪枝操作。
+
+通过一定的衡量标准\large C_{a}(T)=C(T)+\alpha \left | T_{leaf} \right |
+
+ \large C_{a}(T):损失
+
+\large C(T):gini系数
+
+\large T_{leaf}:叶子节点个数
+
+(叶子节点越多,损失越大)
+
+
+
+八、回归问题解决
+回归问题将方差作为衡量(评估)标准。看标签的平均方差。
+
+分类问题将熵值作为衡量标准。
+
+ +

参考资料

+ + +
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2024/01/25/vue-web-component.html b/2024/01/25/vue-web-component.html new file mode 100644 index 0000000..9a97b3f --- /dev/null +++ b/2024/01/25/vue-web-component.html @@ -0,0 +1,1396 @@ + + + +使用 Vue 创建 Web Component - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

使用 Vue 创建 Web Component

  + +
+
+ + +

准备

+ +

安装 vue 命令行工具:

+ +
> yarn global add @vue/cli
+
+ +

生成一个空白仓库:

+ +
> vue create json-viewer
+
+ +

可以使用的选项如下(参考, 根据自己的需要调整):

+ + + +
Vue CLI v5.0.8
+? Please pick a preset: Manually select features
+? Check the features needed for your project: Babel, TS, CSS Pre-processors, Linter
+? Choose a version of Vue.js that you want to start the project with 2.x
+? Use class-style component syntax? No
+? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? No
+? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with dart-sass)
+? Pick a linter / formatter config: Prettier
+? Pick additional lint features: Lint on save
+? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
+? Save this as a preset for future projects? No
+
+ +

开发

+ +

功能开发

+ +

功能开发可以像普通的 vue 应用一样进行.

+ +

删除 HelloWorld 组件, 并创建一个叫做 TwoCounter 的组件, 这个组件引用另一个叫做 BasicCounter 的组件.

+ +

两个文件内容分别如下:

+ +

BasicCounter.vue

+ +
<template>
+  <div class="basic-counter">
+    <h1>Counter - {{ index }}</h1>
+    <div class="counter">
+      <p>Cuttent Value is {{ counter }}</p>
+      <button @click="increment">Increment</button>
+      <button @click="decrement">Decrement</button>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import Vue from "vue";
+
+export default Vue.extend({
+  name: "BasicCounter",
+  props: {
+    index: String,
+  },
+  data: function () {
+    return {
+      counter: 0,
+    };
+  },
+  methods: {
+    increment: function () {
+      this.counter = this.counter + 1;
+    },
+    decrement: function () {
+      this.counter = this.counter - 1;
+    },
+  },
+});
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped lang="scss">
+.basic-counter {
+  display: flex;
+  flex-direction: column;
+
+  h3 {
+    margin: 40px 0 0;
+  }
+  .counter {
+  }
+}
+</style>
+
+ +

TwoCounter.vue

+ +
<template>
+  <div class="two-counter">
+    <h1>{{ title }}</h1>
+    <div class="counters">
+      <BasicCounter index="1" />
+      <BasicCounter index="2" />
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import Vue from "vue";
+import BasicCounter from "./BasicCounter.vue";
+
+export default Vue.extend({
+  name: "TwoCounter",
+  components: {
+    BasicCounter,
+  },
+  props: {
+    title: String,
+  },
+});
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped lang="scss">
+.two-counter {
+  width: 800px;
+  .counters {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-around;
+  }
+}
+</style>
+
+ +

写好之后, 使用以下命令编译成 Web Component:

+ +
> yarn run build -- --target wc --name two-counter --inline-vue src/components/TwoCounter.vue
+
+ +

上面的命令会在 dist 里面生成一个叫做 two-counter.js 的文件, 可以直接在 html 里面引用它:

+ +
<!DOCTYPE html>
+<html lang="">
+  <head>
+    <meta charset="utf-8" />
+    <title>two-counter</title>
+    <script src="./two-counter.js"></script>
+  </head>
+  <body>
+    <two-counter></two-counter>
+  </body>
+</html>
+
+ +

最后的运行效果如下:

+ +

+ +

Vue 3

+ +

如果你用的是 Vue 3, 目前它对 Web Component 的支持有限, 需要使用一种变通的方式来生成.

+ +

创建一个包装性质的代码片段:

+ +
import { createApp } from "vue";
+
+import App from "./App.vue";
+
+class CustomElement extends HTMLElement {
+  constructor() {
+    super();
+  }
+
+  connectedCallback() {
+    const options = typeof App === "function" ? App.options : App;
+    const propsList = Array.isArray(options.props)
+      ? options.props
+      : Object.keys(options.props || {});
+
+    const props = {};
+    // Validate, if all props are present
+    for (const prop of propsList) {
+      const propValue =
+        process.env.NODE_ENV === "development"
+          ? process.env[`VUE_APP_${prop.toUpperCase()}`]
+          : this.attributes.getNamedItem(prop)?.value;
+
+      if (!propValue) {
+        console.error(`Missing attribute ${prop}`);
+        return;
+      }
+
+      props[prop] = propValue;
+    }
+
+    const app = createApp(App, props);
+
+    const wrapper = document.createElement("div");
+    app.mount(wrapper);
+
+    this.appendChild(wrapper.children[0]);
+  }
+}
+
+window.customElements.define("two-counter", CustomElement);
+
+ +
> yarn run build -- --target lib --name two-counter --inline-vue src/two-counter.js
+
+ +
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/2024/02/27/Machine-Learning-Mathematical-Foundations-hw2.html b/2024/02/27/Machine-Learning-Mathematical-Foundations-hw2.html new file mode 100644 index 0000000..598d187 --- /dev/null +++ b/2024/02/27/Machine-Learning-Mathematical-Foundations-hw2.html @@ -0,0 +1,1511 @@ + + + +机器学习基石 - 作业 2 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

机器学习基石 - 作业 2

  + +
+
+ + +

本文可运行的 Jupyter Notebook 链接

+ + + +
import numpy as np
+import math
+from functools import *
+
+ +

Coursera Question 16

+ +

在课堂上, 我们讲授了一维数据的’正负射线’(简单来说就是一维感知器)的学习模型. 该模型包含以下形式的假设:

+ +\[h_{s,\theta}(x) = s \cdot{} sign(x - \theta)\] + +

该模型通常被称为’决策树桩’模型, 是最简单的学习模型之一. 如课堂所示, 对于一维数据, 决策树桩模型的 VC 维为 2.

+ +

事实上, 决策树桩模型是我们可以​通过枚举所有可能的阈值来有效地轻松最小化 $E_{in}$ 的少数模型之一. 特别地, 对于 $N$ 个例子, 最多有 $ 2N $ 个 dichotomy(参见第 5 课幻灯片的第 22 页), 因此最多有 $2N$ 个不同 $E_{in}$ 值. 然后我们可以轻松地选择使得 $E_{in}$ 最小的 dichotomy, 其中可以通过在最小的 $E_{in}$ 中随机选择来消除平局(原文: We can then easily choose the dichotomy that leads to the lowest $E_{in}$, where ties can be broken by randomly choosing among the lowest $E_{in}$ ones). 所选的 dichotomy 表示某些点($\theta$ 范围)和 $s$ 的组合(这里是说 $\theta$ 和 $s$ 的组合确定了一个 dichotomy, 对这个 dichotomy 来说, $\theta$ 的取值本身是一个范围, 在这个范围内, 给定的样本都可以形成同一个 dichotomy), 通常将该范围的中值选作实现 dichotomy 的 $\theta$.

+ +

在本题中, 我们将实现这样的算法, 并在人工数据集上运行程序.

+ +

首先, 通过以下过程生成一维数据:

+ +
    +
  1. 在 $[-1,1]$ 中通过均匀分布生成 $x$
  2. +
  3. 通过 $f(x) = \tilde{s}(x) + noise$ 生成 $y$, 其中 $\tilde{s}(x) = sign(x)$ 并且噪声以 20% 的概率翻转结果.
  4. +
+ +
def h(x, s, theta):
+    t = x - theta
+    return s * (-1 if t < 0 else 1)
+
+ +
def example_data(size):
+    """
+    f(x) = sign(x), flips result with 20% probability
+    """
+
+    def sign(data):
+        return [1 if x > 0 else -1 for x in data]
+
+    def flip(x):
+        if np.random.rand() < 0.2:
+            return -x
+
+        return x
+
+    x = np.random.uniform(-1, 1, size)
+    y = [flip(a) for a in sign(x)]
+
+    res = list(zip(x, y))
+    # np.random.shuffle(res)
+
+    return res
+
+ +

Question 16

+ +

对于任何决策树桩 $h_{s,\theta}$, $\theta \in [-1,1]$, 将 $E_{out}(h_{s,\theta})$ 表示为 $\theta$ 和 $s$ 的函数.

+ +

解:

+ +

考虑作业 2 的第一题, 提到有噪声版的目标函数 $f$ 用下面的形式给出:

+ +\[P(y \mid x) = \begin{cases} + \lambda & y = f(x)\\ + 1 - \lambda & \text{otherwise} + \end{cases}\] + +

假设函数 $h$ 犯错误的概率是 $ \mu $, 那么错误概率可以写成:

+ +\[E_{out} = \lambda \mu + (1-\lambda)(1-\mu)\] + +
+

$E_{in}, E_{out}$ 可以用这种概率形式表示吗?

+ +

在未来课程中, 逻辑斯蒂回归使用的交叉熵错误, 看上去是一个概率形式的表达.

+
+ +

题目中, 已经告诉我们 $\lambda = 1-20\% = 0.8$, 下面试着计算 $\mu$, 情况比较复杂, 对 $s$ 分类讨论(决策树桩的 $s \in {-1, +1} $):

+ +
    +
  1. $s = +1$, $\theta > 0$ 时, $x \in [-1, 1]$ 的点里面, $x \in [0, \theta]$ 部分是分类错误(和无噪声的 $f$ 不一样)的, 因此有 $\mu = \frac{\theta}{2}$
  2. +
  3. $s = +1$, $\theta < 0$ 时, 参考 1 的分析, 有 $\mu = \frac{\lvert \theta \rvert}{2}$
  4. +
  5. $s = -1$, $\theta > 0$ 时, 参考 1 的分析, 有 $\mu = 1 - \frac{\theta}{2}$
  6. +
  7. $s = -1$, $\theta < 0$ 时, 参考 1 的分析, 有 $\mu = 1 - \frac{\lvert \theta \rvert}{2}$
  8. +
+ +

把上面四种情况写在一起(注意这不是对概率在求和, 只是将上面四种情况用一个式子表达了出来而已):

+ +\[\begin{align*} +\mu &= \frac{1+s}{2} \times \frac{\lvert \theta \rvert}{2} + \frac{1-s}{2} \times (1 - \frac{\lvert \theta \rvert}{2}) \\ +&= \frac{1}{2}(s \lvert \theta \rvert - s + 1) +\end{align*}\] + +

$E_{out}$ 里面代入 $\lambda$ 和 $\mu$, 可以得到 $E_{out} = 0.5 + 0.3s(\lvert \theta \rvert - 1)$

+ +

Coursera Question 17

+ +

Question 17

+ +

按照上述过程生成大小为 20 的数据集, 并在数据集上运行一维决策树桩算法. 记录 $E_{in}$ 并使用上述公式计算 $E_{out}$, 重复该实验(包括数据生成, 运行决策树桩算法以及计算 $E_{in}$ 和 $E_{out}$) 5000 次. $E_{in}$ 的平均值是多少?

+ +

解:

+ +

按照题目的说法, 我们要暴力遍历所有的 dichotomy, 因此先准备好能决定 dichotomy 的 $(\theta, s)$ 集合. 如果 \(x \in \{ x_1, x_2, ... x_{20} \mid x_1 < x_2 < ... < x_{20} \}\), 那么 $\theta$ 可以取的值有(使用每个 dichotomy $\theta$ 范围的中值) $ \frac{-1 + x_1}{2}, \frac{x_1 + x_2}{2}, …, \frac{x_{19} + x_{20}}{2}, \frac{x_{20} + 1}{2} $, 共计 21 个点.

+ +

至于 $s$, 它仅可以取 ${-1, +1}$.

+ +
+

出于计算后面的 19/20 题通用考虑, 下面的代码里面对第一个和最后一个 $\theta$ 的处理, 不是用的 $-1$ 和 $+1$ 边界, 而是用的 $ x_0 - 1$ 和 $ x_{20} + 1 $.

+
+ +
def get_theta_list(data):
+    result = []
+
+    xs = sorted([x[0] for x in data])
+
+    prev = xs[0] - 1
+    for x in xs:
+        result.append((prev + x) / 2)
+        prev = x
+
+    result.append((prev + prev + 1) / 2)
+    
+    return result
+
+def get_dichotomy_theta_and_s(data):
+    result = []
+
+    theta_list = get_theta_list(data)
+    
+    for theta in theta_list:
+        result.append((theta, -1))
+        result.append((theta, +1))
+
+    return result
+
+ +

有了 $(\theta, s)$ 的集合之后, 所要做的事情就是遍历这个集合, 并计算在样本点上的 $E_{in}$, 然后用上面的公式可以算 $E_{out}$ 出来.

+ +
def decision_stump(data):
+    dichotomy_list = get_dichotomy_theta_and_s(data)
+
+    best = (0, 0)
+    best_in = np.inf
+    best_out = np.inf
+    
+    for theta, s in dichotomy_list:
+        _h = partial(h, s=s, theta=theta)
+        
+        # 代入数据运算
+        err = 0
+        for x, y in data:
+            err += _h(x) == y
+
+        if err < best_in:
+            best_in = err
+            best = (theta, s)
+
+        err_out = 0.5 + 0.3*s*(np.abs(theta) - 1)
+        if err_out < best_out:
+            best_out = err_out
+            # print(s, theta, err_out)
+
+    return best_in / len(data), best_out, best
+
+ +
loops = 5000
+
+err_in = 0
+err_out = 0
+
+for i in range(loops):
+    data = example_data(size=20)
+    e_in, e_out, _ = decision_stump(data)
+    
+    err_in += e_in
+    err_out += e_out
+
+print("err_in", err_in / loops)
+print("err_out", err_out / loops)
+
+ +

代码输出:

+ +
err_in 0.16993999999999992
+err_out 0.21064865779191114
+
+ +

Coursera Question 18

+ +

Question 18

+ +

解:

+ +

参考 Question 17 里面关于 $E_{out}$ 的计算.

+ +

疑问❓❓❓

+ +

我最开始的更新错误部分的写法实际上是:

+ +
if err < best_in:
+    best_in = err
+    best_out = 0.5 + 0.3*s*(np.abs(theta) - 1)
+
+ +

也就是只有在遇到更好的 err_in 的时候, 才用它的 $s, \theta$ 来更新一下 best_out, 但是这样算出来 $E_{out}$ 均值达到了 $0.7$

+ +
+

!!!

+ +

这里为什么要在无论何种场景下都要更新 $E_{out}$ 呢?

+
+ +

Coursera Question 19

+ +

Question 19

+ +

决策树桩也适用于多维数据. 特别地, 现在每个决策树桩处理一个特定的维度 $i$, 如下:

+ +\[h_{s,i,\theta}(x) = s \cdot{} sign(x_i - \theta)\] + +

对多维数据实现以下决策树桩算法:

+ +
    +
  1. 对于每个维度 $i = 1, 2, … , d$, 使用你刚刚实现的一维决策桩算法找到最佳决策桩 $h_{s,i, \theta}$
  2. +
  3. 返回以 $E_{in}$ 而言 ‘最佳中的最佳’ 决策桩. 如果出现平局, 请从 $E_{in}$ 最小的桩中随机选择一个(原文: return the “best of best” decision stump in terms of $E_{in}$. If there is a tie, please randomly choose among the lowest-$E_{in}$ ones, 原文读的有点稀里糊涂, 这意思是不是说计算好 N 个维度之后, 返回 N 个维度最小的 $E_{in}$ 里面的那个最小的? 从网上别人的代码看, 好像是要这么干)
  4. +
+ +

训练数据 $\mathcal{D}_{train}$ 可在以下位置获得: https://www.csie.ntu.edu.tw/~htlin/mooc/datasets/mlfound_math/hw2_train.dat

+ +

测试数据 $\mathcal{D}_{test}$ 可在以下位置获得: https://www.csie.ntu.edu.tw/~htlin/mooc/datasets/mlfound_math/hw2_test.dat

+ +

对 \(\mathcal{D}_{train}\) 运行该算法, 报告您的程序返回的最佳决策桩的 \(E_{in}\)

+ +

解:

+ +

读取并处理原始数据, 然后送到 Question 17 实现的算法里面计算即可.

+ +
def read_data(file):
+    result = []
+
+    fh = open(file)
+    for line in fh.readlines():
+        parts = line.strip().split(" ")
+
+        x = [float(x) for x in parts[:-2]]
+        y = int(parts[-1])
+
+        result.append((x, y))
+
+    return result
+
+def train(_data):
+    dim = len(_data[0][0])
+
+    params = []
+    best_in = np.inf
+    
+    for i in range(dim):
+        data = [(x[i], y) for x, y in _data]
+        err_in, err_out, best = decision_stump(data)
+        
+        # 记一下这维度上表现最好的 theta 和 s, 以后预测可以用
+        params.append(best)
+
+        if err_in < best_in:
+            best_in = err_in
+
+    return best_in, params
+
+train_dat = read_data("hw2_train.dat")
+err_in, params = train(train_dat)
+
+print(err_in)
+
+ +

代码输出:

+ +
0.25
+
+ +

Coursera Question 20

+ +

Question 20

+ +

用第 19 题求得的 params, 在 $\mathcal{D}_{test}$ 上面执行预测, 并收集每个维度上的错误情况, 最后报告一个最小的错误.

+ +
def test(params):
+    _data = read_data("hw2_test.dat")
+
+    err_out = []
+    for i, (theta, s) in enumerate(params):
+        _h = partial(h, s=s, theta=theta)
+        
+        # 预测测试集数据
+        data = [(x[i], y) for x, y in _data]
+
+        err = 0
+        for x, y in data:
+            err += _h(x) == y
+
+        err_out.append(err)
+
+    return np.array(err_out) / len(_data)
+
+res = test(params)
+np.min(res)
+
+ +

代码输出:

+ +
0.355
+
+ +
+ +
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/404.html b/404.html new file mode 100644 index 0000000..956afcc --- /dev/null +++ b/404.html @@ -0,0 +1,1086 @@ + + + +404 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

404

+
+

404

+

Page not found :(

+
+
+ + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..d37c6f9 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +www.iuwei.fun diff --git a/about.html b/about.html new file mode 100644 index 0000000..869d224 --- /dev/null +++ b/about.html @@ -0,0 +1,1153 @@ + + + +关于 - 挚爱荒原 + + + + + + + + +
+
+
+ +
+ +
+ +
+ +
+ + + +

关于

+ + +
+
+ + + +
+

您好, 欢迎来访:wave:

+ +

这里是宋志刚(Sidgwick)的个人博客, +收录了我学习中遇到的一系列学习资料.

+
+
+
+ + +
+
+ +
+ + +
+
+ + +
+
+ +
+
+ +
+ + + + + + +
+ + + + diff --git a/archive.html b/archive.html new file mode 100644 index 0000000..5190af2 --- /dev/null +++ b/archive.html @@ -0,0 +1,1543 @@ + + + +归档 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

归档

+ +
+

2024

2023

2022

2018

2017

2016

2015

+
+
+ + + + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/assets/android-chrome-192x192.png b/assets/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..91cdd01f14fd78e44bcf45a9634e2b187df212ce GIT binary patch literal 6672 zcmZ{JXE+>C)Aq8vSiMCTt3-*;CTduH^%_Kp7QH4`Z>xnwTV3=TA|iz7ZIz84JxCBl zh~BOKdY)h3cU|xM2cb3q03Q(mVAC1^kj?@C=)H1V z^kwf1__msA%7ELLq~XrH6CqMvTZM3i5RVj(d%-*sfdFPqufpzH8~8Rl_>@J_+o3 zur2L{e7jsc>mZC5=_ca*-$X72R7JikxWYH2a;Iv;ahN#nEkI#Eui&HhmFBxd6-5Aj z$QD25>O1ytu=ZH(afK-e)Z5K-<$A0>(M26Ln|li+G7@0rAm)Zs%7xS3Ca;9Wfs~8O ztq%y@Zv`j9%izjIAR}|T=Mf-A>K8Xp-#Sx5i28J;Ah3havDv=YT`TX4;P_2AC4GK$ zkusYLOz7xyQ6^kHi&ITnCviY+n)8B>8VUe|*TM)z?-hsE^y&6cLbBwu;(PTT0Jtdo z(M5{U#3PoAZP6Y1&-)%LZTUNA=0bVAw4}M8lKl+LLJ0B)A_m_Ltk$MU|D?FwGTrUuCj^cOBIt}Iy-F=>g zvL`mThG?+{>rUW2>5R<0m zX@9-cXIfznoLD_1Uu9plZxW?qCyR@S3WroQU_%(jql!w2qc;;=+jIVDZyWRj9Qx}f zO3L;agnJ?>i1F`nKQt^jSW4{)GWcS0vy40Y^Q>v#wvN`>E}WKB8E(t*;3!r#`mZ+3 zgCaEgxxB8{>flliF2zC(09P5Xk~slxG`DZ_oo}t4^*?H|U=G#;C7V2a4Qjr%x`*xP zj#LLec>MS%bn7E#XL}_8%wP9$+q~c7D};<-FhSzdbL7B%=vehqM{VXgI_q~H4w}o= z`YJe$D8vONa$1u7Ss_NhweMQ8wP{P6Lio1uS5*AO6x(?t{r*JxpU+8QYSox<5{Xj{ zQh_kR4NV}momDh3EBD~Zz+C5X7zMw9g>|>3-R|pfgQLdT!8R*K3mS`JHZI!+NEg@P zOoSX!thb~Xt<#uw@t^8~j-g9Z^MX^RZE9aLnS>M{{+#~v1kSX7+NU|CmT&JpF~FHD z(jFt==~@jI9!|a*ygxjhevMS`jO2n%J_OmrmdM3_oN1#zc`w~#nNH%U%|{D^pHM-w z?n}LDVaT}%IU`}%J0Gp|D+=gp2`BR=A7fSZ`nb-Z%bDCeI>~wlhv)now<+ANF1X40 zy>MMdH4DB7-J6Cbd}rV$YR4QUw4<_0`;#K3K=p5yP;WG@u~^^J7QEeEd`x!kVm)xf z_`CQiv8FWK!-4k!o@|hCAEcfd`(AD{w4>noB}r9P-v0Bv$sAu$d*Zc4kW_wJ?bWc} z!&UtR1GjPH$L-J3M-(?~ybu#~HdN>a`*oysZnMhc`&2QB9f&vFZu@VuS#~DdM*p$J!S{~l0119+ zFBYaNt7tq{mo{`7u;qhU+5zN8^`dhac&)e(CR;;xL%jRin>tJlKdRpk4xOJ>dc|gYnVt^+NSZMHR%RF18{l=mc|R;eXoJ z@tk4s2n(sIC9qk&|EpxRk13q=ob^yV$Qmaa%QmwoWG3B1`0}XkhMkTJS}XYtuJDa* zVoz8Hb`F=!sZ#6?oiz|NZKCPzX}Wqhmv>wjjLtO~^U_p(-qBzIrQnS2nYIbO^}o>uNJ z>S@$Y9P7i$eh=`av`9(FiJm+4>nJr55j$3Vw9M_r+`hTu9dP>{gG6?wTGZ`#^kYNlqsR2 zvI|c#Txz?c=;N)atw_gbR%IZag&xWg0CNhyO~eONf2ZyX-~3IBR9D;U<`VH!tLFNd zby_W#X+U#GLUUFa&dmaXqIp<5;LlXl&-N6ST?A$ce}WXCzI3I z$JO|5p{cnz}g&CYcd(~D_4#s^HjHM~H z_a`WX+S?2WX{9u*dE?`&`--et*45|L1|wH^_J6I3Kiy;HlZd1AEvp+3Hg!rzh4LXR z=juK#(C<6HmEJLl(2iF3^cp)LEX8goBv2e1yL1Wp%w#2TwHh7Mq@C)SP1B{|2VsPyHe##B&CAEErs6)8=HN<(rp9eIZ<`jaAW%R>jw!%`Okj07{k!wu*0Em$e)rGpUk7%C6I=t4X3h5iTooLK4mm2om1Cr zTvJ)k=|MbEM}Ay}N?kEfv1qAgt^4fI?)mf~pf{6walWb=`xwnLl^Xoh@4NIEJ)U=l z3(<>yCPHr1B0ZYv&^ z2BsK}-Gy<@>n$5j%J}x`ZM)#s4|8H7`8l=KhPAIjJ>D-yn&A%OD;j{e;h(a8ryS15y z_10S6;JaB;JONg`NZmY!&$-?sXv zG0?|AoQ>CvK?T|bA%Io1r#Ct#%Mxv^2kR)%BuuPMyeaNWmfz?bRo&1#7aB^VC1nvK zt~PaAQueuhTMSN-pRKPY^pIskr00%uhj7oJ=WE6Uo@iyO#Bqk*iV|iG&%+Rp$KEHU;O2z3UYIdU?Xw_Wz?29z5GMb(r3y19c-YFUSl$*O>FV9 z#)uVUB;FH5|5B*Rv13Jios)Nb~xqaHd$Ne6{* zlc-Zm7t)VYv|8gbN%GcPHsLd%8dE}9Nw4_GQsa8~A;AL69lUvl{lhUt=lj`xr%GC1q4Edl(a@FUHwY!+bv^wdbd z6GHRRV97lVqgj7FI(pa`4TmI%g4a3mFfA*2URsy{ z^{#BBHu`7`pvv^$7@jKTbdoci&bSZ|_{zD!a}$O7LD9coBfb!HOr!vd-so(T*y2XO z3=A@j-xspjV3s6T7A-Pk-qi!H*_k>fY(4V6Xb}OwRPKY&N67%{(#z&$>Mr-+ z=Lq=6Fg-_7DIKD{2Q$M0K>xpW-TIWQ zB5q`8s6z49UEvqe6jbK2Onx?`NvI75ivja-PxJmVl5^`{kBZ}{nDFtYXRz)NYA!#> z<9Zg_O+0@^Adf)s=CBtG)w^o-dd143Uh-9p>vjx3%^_cl`i%HogHqW+@6W+-0Klqls43bmOZ(7$UDaCOd}-%+?YVPBz=U zqtQ!XGCMr*^n9*yjUNnqM^>JFz*xYa2aAkzfmaE-#YTX7lB z8uqw<{HYs+O-B&3me^0ExlGg}jio=a>FE?FGCAlevC^^lI~!2l4J`@loy$_l(T3s& z8IK|ktbdhlie3k-s@V1{3swri^wn7FD(oI#*08ByD}tpV`c15qK1hoR9ykNC~T`i{*ZrSS10iG zn6T)N#c{!?PjQbnsf5<%J#`yRj zfuD^=^;9=6Kil?u@k)V${AH}R#d{G}Y4NNEe@8kbD}7j-jGQ)08U$}z`G!z+HGwZt zC7&WZSXKH%uF>-h$I|yNsO04y()|{td*sAdf%|wpjd5vlL3u;P2_b>5KUq2W$(;Ib z2{caCf-ddPOM{iAitd}}rg+gY?q_Gec$|zvAyT;bzjUyP(e~eb{N-~qZ=Kg!i>u3t zrB4dwVDVoBGg3miphg=aE{&7*YD{A16{nhZov*ULL!aQ$sCQ<7z9=>;LfzKi6L{+F5NuIFrH?5iZ{nx(5P0zthfW1{Qt>_~N@UHBn zN(j`y>Z8RU2fi`!Y9rS*=RC!Q>Al1>&lC;|_XGHxlE;eSG33D4&&1D~n6oAPO-P*w zeC}7suPep)evBSULq*}pvQCn$iwuxfAuY$TzFD8yR6tRGzpbw%89YPc*?T;-NDV-Y zbyG4BkY<@#%FtFhJ>P2!PxnT5dfss8$7jtGk=+ft7iB&5LL?q}nVf1tRafHYq*g67 zeS3XLtX$X-+R%nh1V0v$%_+srK7~)it<@~&TX|?TqJF7*QMo*56>6LvJ+uWi*d=Gq z0${Ey$85f69O?dr5PWE8Jwyi+Xv3Oup8}!^SWfg?lg>rYNQW|nD)fq!NWKCdWR>bK zJ`4QvGBV^y#Io)H%F;oe`^wdT08}_~J$gmn9z42Mz<-O7iRWf=I-EA(Z@5^d>Q@gE zPQ1_o#eP$7Y<1v&tjevGrpGZd7+J>SxU~6PB6lt#xaOG4O#yf)qA=aN!a4l+-g9#t zU%SCYZL_XvRbA7vbHY{gjNHL*?*-HJUilt?+#J=DNK%LxOU0OV5hV4NPjjX>WbylE zuT!4(q`s~#Idrn%AN#Z77nLkbY(*KoPN*B& zI0y#d6W|j^0I0b-;1%7k6S%cZi*7#59EN>qge_534H(>P(p-37?X)4&DSE z756k6uBMCUC$rl!3lhgcY}X1$d{Y6Uft6(HKpK?tj(Gh=`tCYFkUsJU z3tVO&6MrToYqZ;EgnN3<0rB1pmlNk@IPN;+4Ac&m4_^L$IZ5-BP_3juWiq8W(QtlR z`c;e1a0UVooGxfr(3nxZz5E3;yD;yag@rkcQ=UbhRIWau6ydABsvP@%18#j9=(du2 zq3-|d7#3B*qccp$a|MF@=Hfa*g+0hWM-~#$m$erk^c?nNt+d)y&5$UU?K}K7ml4lR5aDg(Y>sIdC<~ zA58-BAV($eyynGA*Awrd+jq+4f9EW4)lCu&xY`2c98udH+KTdY`nHjUFYhNl*oW!r zINF37(CBFk#eO`|-d)-ZlRfCPJ~+qdY$GH49-pf82G#(;uPnpn7y2`VgTV$lxXM?w zhFJ<^WcjF#BBmB$eD1zAud-U~&6o3o+I72wR>i<~Dt0z66GEsW?$<==78Y8kIBp-! zj7=Zi3OOg<=AFvE7foPdIxv=Y&tLgWFH_XmlEX z|1I}C91gH9;mdVLM`H znME+n`wH(_F_68J`AC|x!`dYmrLoodJ;eHV)PuYw z_%to;7)kt?MvmCtz48UZUu1j`M7^!<)Sf`dGS4Dx(C43xGrd*?8h$RVCjQ)lb@p6! z!%QQu5E@mJ8(JM}%sAUu`#HboH+oI?Q;^C{!VQe|Ug4dTYR=En}N# zNQm)`d^7=(c0WgMWVdeH$FT#U#WT%Sw2a|FIfwNw`-}DW`_YZ==`z$haN7*@+GY8o z4o$L`2Z@#ck7)d)_y;vk5+QUXnC-Wrcykn8RCpw|yZ@5bBTt1DjnpEMQB z>$wHu-}Wn>#A2oH{xlvSRZNjDY>@WScCYO36hKTwR8mkxLQq81NK{N(L{wT#l3(O* zBeE;E5cB^L+&y17I|TgSgaSKznLC0>pphxkz{U^i^~%%1+07n`4DhmtI(s4Q0095o z)k9LEm&UxjgStb*>T3o7ASa2C9SIF5jbkOr!Zg7v%ra@H9^hi0exdxv{jdI^V6 dh(Zk^z$QHv@=)=H^&H}jAV~vWMxxHvPCE|vYlg(6h$FfNhC5NDjb_+zO%^= z*(2j{#`pC3{SUwEa$U!{@9Tb^=l!^!`+nX@=BD}#wA{1+05BLD=vo2*gz_&0K)@&; z_78aVMwx&(U(?qG$O)-^ILbHb07H|j)Qi+0J;Ms~6DRX1iIb{~sEME`&N(_<4213LcelRDt}n)HKT(BiHEK-=Nh6)BV^ zgOrSdp;eUc&TZBIN7}iT{b(E~tcBV~foXcZyOYo7ipd!Y?z4wGehm4`SPvZtDk{4+ zDd_AqQKIeg+{NQ7H_sW+>v1t-qvX=%Jzk!*%3*LBz*|jeV(!TmVQLY05j9Nd5L}ABlT2xv77H)Q%m%UsplEuyqX$kpY;y@)bghe~iBsS*Agc4Ru!La#+q4 z$7vCszIE(Cfoqqe_a4Vrb_gWzUS}f3 zXVS>3V&h51FtzgB@20OIG-xAQ14KLvGqzcdMVWAa>8AEls=X)SRve^LpW~R?d z&WUI*mpgv%gI*V)}HD*R)WHI?b7=Q>s5H zAn-hT0ShZI>r1>v{Cr!0tC@<8ySAnL&Yxe%##cUkJy_mH*e&<77ZL0B{;9I507$qL zohC*VvOxnFAbJ^S&;rO6lE-Z7YS`M)%=^!7JlVEgs%AbE|W~+Sw5yFmKH;yO5sXg0Bf7yE=lqEQTGw_hFQc zBs0>W84a^AVcV1iSmiVR9Zhtmdh>OeO0_p?Z`I8sjO%m+dqUxSIF!$CKm>X<9#-Jq z7vM*_e=uIyj{W`d_KEB6MvZ>G9`8}Ze&Q+mx0cJf$P=SVVW}m?GE`fZ3UYp_(D*5_iNqEgnYJ^`dp zn;ZO`2fga+(&gXEIp*6RyS41B|6@K@kORxxKDhBs_`l*OddJjP9+5jIakk&e@HnHI zwQ*IYFxxM;&+sSEWWFER{In>PdmUC6Eul;j4B6_DAq4;-KDTW{QqTpRAN-|?uF6SE1#zn3woP5;4T*UBvL91&h;FH1&_Hq z!{fF>6GEr0LkM)+$1D)S_Bx1y?n1}aL=Ipk|F+5gDP?3US|K&Q|9 z{xy?}eBR`6D3t_fd&i#HsaxgQ21obE^x=+)8a?9!Ue=TBKnQabD8~v(b0Ij>ojX61 zRC)TM>*}mqYbU`u^62?Z{;HTwSt3vRas0vcGpDU!C|^QdI_a`wVe-SU>BXD(NDj0| zul;fkE7m{x!hKMh>IOnXK}OO5*V0YY=Xu204s2|ZVRkxt{Pjc|Z8L_91~qo?MAV;i&GD7rJCV2JXL|KYsBL z9lHNY%(>oZsyn^DfqZ@9#74v(g{{jtI?*p3eeS&NL2|f2VOBC-)A5mbm2<;YAsLjB zBycB1)9Lg{|Iu{x+_HNmzW>#!SP3qM{q>0jLt8CdLF^CA^%N(+qorA&-sa1{qnipM zBM3K$!CgO$?!pZqr@9y5j$BLQg*pzw#Rrj*fjb|~@B@EKM&fK>;V1L+PE-YaWHZP0 ziMf3%x4B&A|IBe-5yfAn*}+s@NAnDGz|Z!9L|dZ|+M!_<4~-0|oXb?VBKjU)nDC%s zZYe<19gx#2D(EPZqgs64d|RG$cd=Qw^!#18IOiJO`NuciCyoMsR(K33PP-}5`PdLr zj?a{u7=4;&n})-_hw3R+E80RmN`P)|+zL+nh)?2TiqBExhmc%yV~BkDe@L$@OG%!p zVdh5$3CWf;=y$(g4h7J$S6*tYK60N88`c!FJ9b!Yc+tJ6`KbZcqYEEB{dOWcd0e{u zdZp>tZ11CuC)Ra$OFptpb0wj7#Ge2Nf85&s#=^>UFCoV(|8VX}`kDIEdF~x$FHL|y z@1!W!rPR5cv>E}tH<=Luo8E{(hU%t2($Kx=ilfUvh!lvK zjOZq8;ETE*Z|!sn6^!94g1$GK=|uNRf5LPh_WerfTmaG#R6UGq4Cj&9k(tVl1aHnN z$B>N`(wz<5U&r@8ze{HsdViflfDS`6-DsB=;e*@U2f;Slk6xtsppkHmpe1%-9~!E) z07qRp_R4+mt;#$bW3@!4`^a;0-DdkkZGP#aO)#sRQ&YJdmhvSQ zPnV?i99w7U0UGoXJ%yHhT0On*oLt_C;LMb(HC(0LILD4S13nymh`Cs^GqKw`7%(^d zN>1Qz&oR^hGNu;=FYuc2DO@z`Cp&H6_WPL=f|6Hl&0-;rZQ6Aq5mkmq>O4lAxRK_k z>Tk#8F4_U+3Xb<#tpUHKNOijhAEX*TlH%s;YP$Iw?atnhrDTF1`p5U4)a*Fn`#*f83*aB{RFTg&1sP=H z$zBzg|5CF#>_WtOs_xQ?1KF1!y=lNj8xdskqc1xE$FsEtQ;whd5xb{X&jS{!+-X5LI9xI`)ox*81-hYuV$4=YABP$xFrZZT3 zcB9^Di&-0V@&OE6lB~o*q#b52RvpSNakPRb|CzHX--BfBhHqp)VlUWRd8SH@rn2?T zZ8E{#3_Kt`7n#?+kfzn|`ZH25<0uDLjpyhr|8!i>dHkL~z~;#~{ptm5XN=E-J4JJb z%tLgv!hmwgMm)9mSFyo}-ZN-cjs67uqIr2qVY`QYrLz>3I{^<^R(g&T7O z*poykQX#J|N^W3A&zgu$B1*r$CR~u&e2fpBbm0;-eb8idmOZ6CQ2kK^l+=Hwj(zPs zw-($_+jUpd5%cWW#RtQWFRdTgw*OjV@G}zvfu92m>vwGV3?4Cx(AZpHhNC`>ihVrN30&y!$E{)fQ)gQ* zdQ}G1Nc#JpC*; zV#k8}n8p)X8yTmLY~V)xP&L9}ANVSDdaG2aq#rYj@&6MUk>Y>9`Ko^OwW$1UsD0oH7Tx3UIiwN2 zOBUFH4!O>yjkfc~V?9AP**|%fUsTofbd`)^!?DF&rGTr#neH#5#P)>)^5x)|&75k5 zhL26l7W%-w-HRmumDv+1!J+7RBTh$zO#1jzw=VqpStC9t=7Z~8a^0etvuB>5s+;5}@7a`pTX)zCK9jWdxt&1#qD=p;L={vc`L zUQz4CI(BQiJ}0+oq+fH|&&);9Kttu|&>Cp|2xjaAfvs?P+*|tW z3J!a3GSh_qGNG^Q>QG_lwJ6=2r+)sH&u~-s{>qX+k6|ReF%Hfiz7Bc4kRe+%$7vN_ z)Z!r1V_*3g^F^T80XB>w&|QYKjFYSqp5=uBclvPA>zgTOvT_(_B)1cjf2F@St2w)Vzl>pyrYBBb zU^@kW&JB|dkKoM0*Zfw*mIjLlZ=Wy0z5gYA#+>vl_Pj8(NdQ+dy< zr`f<+M6k#KRACUS%FsS-G%lTD36J}dAAUIwZ~@uiAoW)Hc4wfD}iMd`WzNeIQ|3s zT?#G9inTp++vY!G51-65rq}Eh4Biu5$W2P>8Ty$X#ouvciicBMUd^I6&UbmdtzcVt z{`B!tL!bb+M~fcaCAE`hzz`m77#c2bBL&_Pq6tp=Hp~7`mKD3C`|d;ol=&H5dvtPS zAbbVUwqtX8B~r0+O0lQe*EwtWxHU!~pTXd~)}252g@OwLC|W8NjI-m4mQS0e&QUQ- zJ8js#{TuS;`YZ~sPtwfIfwW@A5~ws2RR^=9`bHk}YoW8nq=k-#AEB^7`5|4pb2Bo) zya;gX)K4w=DXccP%5)B$6_)!c`mjbipHr_3_m2On`ZI_KV*yFRg4y^YV>yp5*kSV`^AE$!q2IQaX? zzy6F%0N9)p<8O>!K#TrBD7NjLPxcwn(e}s$6_3D$3fMIiZV0H7e^f$T2kANCsfDSK zwC7lHaDsFx{Jr=7OZ=R$GGAVn|8B6CQc{vbQ8)j zFE?*%We6mpD+VrCz>}0kIYUGFZ#)$o$ zhrIq-?b@%QyH?CC%+-FIT*RB^lmvM!Y)5Ghj z4P=D7$^cnUrs+k8I||{HJqegY9$R z8qQ$eJ5QKxV}4M8DP*#qd{r?NTeU(&gm(eg~%iB0>+!SA3`y z_kZ5R>(Q;(mpN3QODOJoL!U9i$Qj)6tIo&qb*b!e-KE9rlZx@;EH5x}`9_qrPaJ42 zpHUgxke1tSdtx@jQyiWoUB^B9Ug{zMl0Jgn%VNfm)mhgo?29K@F}fT zKO-t;$*al=hxOjFedPLi#LC<`xr(Xtb&T>P`Cz(%&8Wc-`}ems>!lCx_H$@68Qfs1 zk~74zUcP1xe@iFNDqhylvmICC!(hQ)Hm7hDcYtG{)_};A4)@`Fe=V-gXf9^9tS{V_ zi+qYHYPybdIBZLjWHZgY3VDk_k3SN8$m!U)Tydai)O1NA ze%3ubfYVealUCeMiq$po5|5c1+nVi~9K1j@{=X<@tNTfZ*mDm%UoO^!tU6~aCWxz? zF0p88^YsrApfaU1w!vXU|5k>W9JP~M1GfoHD}oD#PdOluFM*c+TiJY&+(<22R`Ju< z4ts+ym9^jWR85WCe1U(9m$v_Q;!g5$8k+7co6X_9F$to6hL=w;qe7}pY#0s3pt{-E z)!VE4_rAD@B=?>Kr4T*h`*F~g%7KU;B63+i$Gvsb)I3CrV5JR9mI9x{QDQ781qQ2@ z6zGwg%<|Lc;zIxUe5^`7b$oLFaX!t*m&$MEJ;y)i^>}I9+R>!hKltgUjyeKa9h6t> zj8C0RUvu>4~fqQXuJQ5PT`U3a^NlT|sA+bIf9r27> z&y@_z(5>bqhb=iyoQe6GW|OnXUqM$wW&*r&|LHBWqMs(>@3NT90ni`>eBZTTUXYb= zd(}b|yXL5-5otDYAZ8ODNNgv_PYNeHkpgF(yeb=%#h*tB8KDJXs1hZc8HIB6FyrTs zoE~KDH!b4QCPej53vCA#U*-bm?5f0K@QNpcugwSWz{xw{|%`v3ADgp59F{L zSeae1w_2+c{PF9?m)v68c<^dE7D9s-2N#pkVeaUO-e+1AfbG{DREFJC)YFguZ6N4m z^*jF$zuljA9&8!lfw&(qRwE$Ij=|szD7$fRk<2^kBH~)uEKd|(QQ26*h$$H!+1$|f zy}+;Vw1Y3!C3zXjY7EF_1JyAFkWMAtoLGIfyJmBR@~_-SXWJxex@)vqcZ`;(lhZwf z&J`Fio6CSb?`aGI0^E}ymDV){7m1#l%HS^(gSXncyo?T~^L@=KXI|Xn>3z&X#kzZ! zHXCc5jx04FxiKHY(Zeq+>ruvdYf##5RN{_>lI9?0I9zmCjTike91EcZ{`&@96cxPB zB=b~h6~^P5`ixU#rMddoLFJ=s+2p-SYs|-fjw|%yZ-KXus1w~-HObA}&5ire7570VA#g3(H_z7+#wu>kCOtoUgMtKA%vYOVb;0-FnzK;BPvMC+yJgUYF||;(Gbq$rFZXo-baVNA9LJe`+s|(lj*SE zXFwbqwlBJ%LDM|0>@y_hD~w*TuVV9_eNTeWG>iB3oskt5x@cf|6{M8{+M*p*7z?zc zJR2QxuTy{U3lB?Y`E1qK?`mrniecCDt?^uh6URN@`~q)*g86#I5^-^r)jMvGRr*cY*nzB%#ZbN7<0ZH)I9-c%I^f32qEG0aUWsbn+NY@VV ziXKq65c)@_=i3wV%V|)cP4tWmI%yzW@m$>{X%jc4YRiVmKIOoZaMj2fOwSCRv?-k3 zQMrnjo97}m)$V*7O(vAbL!54OrEQ*8No>QRh&9x6Qi1 zTA;w%|L$wrWhaTDxES_VTjM8N-daZp8t3z{%q!AZP}l+~a-Pjbf)QoKl(q|IwehKF zrioKW7W)OYQKwNnnl(QIF*!C`qi=weL^D7(3VmgVb~fpGYZ=Y;7z<2P8RzLV&`=Zq)T7wVD5egh-PvWc z4h?MW6RqL{;H&pJPj`sC$NJt_UAx^iQ+kGXn(kY%JY!G;34}x;5MT%|qd+(M(2UMB z^B;J5jxL7+^Q|oFAXOeh${&$tcdpi{K0V4sGgdmx7gnuC$%i{-3Uo5-Ic zEMPw)+M#EadnHmZ%!9%UX_Um8Ql*?2>xs3bYcaW>@ZO4<_5Qh0E^iHz*$Jf2>y8}wn8c^=sQ2RmQ?E5*Idn*r87vpag}AL_d@KA}*Y7?}|gflzZ((MJIRhoJYw zXnqerpO%!_#<(R<40RV00bpwxk=L+$uRmWq8bUZLH}b$hP7kCE0vy5UQ>oL+DVeLT6Z+6eMI}RArB!q_KoC)YoY(zR!#<=<_c^3~ zbC{{aZ(^Lb6AhK5=H>#A9O)0uQy7e$2$1Opdskon1s0#5$HRcC|z7AN1 zfrT)0cC5Hu_$P?F z;=h^gU}>{pAc8{b3$QFt6S&akbn%dD(nUCN8z%i&9oxK3%S+Mbg+F(|Dbqxy)ItSs;RF|Sauo}JjZhrPMEo4Y^lCNdd!JOh|n6(1B=U$ak zZCbHAuE9|Q5Y({vOx%b6KBC{WoZO~*Xgo1Q2VgxeRHef`&VUhAhPX_t=!3Uq;05X` z&;Oc0yM`|Q$rz!;^D?({m<55M1m^-K%Jq(=qZplU#>F0f9<2a(TkSk`hgn(^al z`KBO~ZV=&OH2(PBwV_f3dNwI4@2lDWFl)s-A%?nj=P9FdbjVQf)czmoZJ0aN3thwz zH4`Tdxb_^r+t-9}O?`s;`et&~T>>mU=g6?T>|9`Z(>_gIBzo;>echZGJoPD%cnfO- z86nmrTt)uc`@|1~=1}qSfd=lNTJWxXF9Zqi(v+@48t}KQBTZ?URbinSR3gdrb8BXA zRiKs>q9WI#BTiPdR4!(T%ajM&V}4?bL;$`6j&NX#V*9vuHc9V5eM4X>@fIME=cjvw zrJQ=}t&mmN@g)W&TS&|0By>JIBuR+nHB$l*@t%s83+#Cd>d=@Q0*Me7Z{uuC>QzTq z7)qoU+TjJRJN>`&W6-*?1e)`;a2sT4hA$Otf=nIERb#iz3p-Y!=FGkfZ;xTH-BIoj z-}%`oN2e;u0{fdb%#8iKC-x&M+?E=ZoPrU}@V@9+IR@&PP((dv7YoCRE3|THTOrG! zHtLFjPhn;OR0VP9H$A(}AE7B}Kuda6x$M|%@0_(zVq+PH;NjEaom6?g#fzG z7DiiGZvY-_1^OL=SZxTJmD{Ijh^?w5 z&7%T@^M&X`;sqH0RWK3h31$h0$-t^vTfa75$J%E=o|FN*Czq++mqBElsLgmKv`zmy z02H$S57Y2FcGLz$V9eP0h?<%@4$v)JU|Fzl$s30}eFG%+Ykh~Jda+~qP1qs{K!_Iw zqLLZLS-Q*wTV65!h*GYgNTB|X8Q=UGEim++;)<~1Dz;A-tTeIwEv=v)gI1z*Q70Z_ zOSLIzA?I7W1F;BhI7hjpd@q3Zp`qlBsKoQbp$_2C-4QqzTG-62`*3*Z;BwSd%2z<< zAA4$i0x$f4rUWYl1&fb|?j(UgF!6x|1*_^m)2vsfDD7+XDLggQTF7hv)f>IA-~gzw z7Uw9UQa)UnuT}<0^Z2Msfstp-IG|=-kg^4)`&|F_VDia9Gguy8@Tuf0AHXjHV$>}s8Jn$0%3!=B$>Q2so=h18MFpmq z{op^RB?b37`fu&3-gRIoo7@W72NJd0xwU!D67vvkYcyz4C=jO&yqOSz6_n()tkD2i z-LUg;`V5!|?GryKCxUtgc9<2bPzZyGR`&AKYDvZIg`c6q_JL4+HFDU7D`On$W%T4} zbq4;}m`cwAupzXz6r6}q&dMoaK}-#x1;-3rNwloYz?-$w1Z2zLE3%0?GWr=aupob3 zkcP87Q~eS}Y7X%eP(HSL=Art?=FvZ0851bZihTqgT+KMNWQ(Qf%kly(ZAQzo*mr(btpNQ9Q3Z^H66K0E ze`sc<#^OdFuEbiIxFI#$BkmV#c2 zLkR2~rA!PPlAC{(C;?0z;Ar6zC0EgJs!Gj1&{gBbEU``Bfqy)`zXAk71=1<%vlhQ6 z!>i;gPCX=P38FV3lTe|=2UU~RVe<$BmzYc7qr;9@0{@Jv z7`#;b-~!x$m6`agcgf=o*DBB$Sq=cg!T=!01sH+Mow^Z`>p30;QKH_003uYAocbIH zI0fPav5Mc{(`SkEdPlC`{fvBt;~OML7yuB8@ke{oR0RqKLQu(6{-Qn>sRc}-z!uQ5 z^A-qGC8s6=eInn0GRi=c!BGQ32wq9bt>y?E(P1ojQ3|ZJOJGIMlBtd4$zJA#_{?4k z5+VqRxk`-`70*IIgax}sBxtHYQ2bP7+HortB|@k-234I&KH1-@5t0I#Mb zQ=+_bV69(L9ExV8zQ40vKU!3cAE#vO4II8ZtK?@$EP)4;#T)a8yX9)wP!AlxwyM$FDC9K@J6lSLzvhcO?vV-bJg^ZUDd+!K)ETtuT zBQ8^PQ(9j2HHqxr|Ed{xZBAVhR(8&RAfH|#r(!9a&V>Q}`3MgIRJCZsdfq0)F6S~M zm%j2rMGJjO3$5`3ID7pfX6-f_l_ghLLSZ>^ zrZhaBiVI!%!4O9LyqL*CS$1rO>Bp?W0rY=_D;_iZlsF)--Q>*qOo*7huG8hzuwXp* zV0A0bA14kxLntjA+j|q*_5_FuWJ)Zj%Oc}3v@wm3-PB^zNnU)AJ6ERR{>v&t&1vF6 z8==YfbLt9VZAM^kH#I7YoJQA@G66+Nf$`h+(*Jl{%D=ApquH5ogHo^j?~}{A;e}mi z0l`=Ju*lOLKT4bm^ke`Bv~WO|yf>InX6(A3KIc|p4&&8=H7bdF@I=b*Fa~VX5yg(w%yrZyX4oZp|Sh698op z%{Dp}hF7l9*GoaYIu6}wIJEZu`<#z_u6-L!JizfAn*(SA?LboS5wyxtJAsPLGu)bQ zIh*WDhyIUxs9^kKLfuNf5%NPv*xa7ku7nh)lTtyfm9_iedJC5&m}STheWMivwnr3g zK4dC-f?1joc*AUjrMl9GFmN@11AjyvRsOEvTjMAZQi{s#&X|1?tug0A6l#>3(+KRT z1xE|otLB8l>|kIg>8(Gvgp4DaZt#8D8Z{+d0u1g0(M-m{mCq<~EO7GuaefaSl#*{# z60BTWpyDD>Hy&ezP0~g-`faP1c5HwTCCsUG#DU9y{IP*+GBb+b`8l+78w5P^1S+X& z&jWtp|FSk$TC9`2G~#nF#o2VM9es;^1te*-3{hIhsIljuqUV7m=Y^)>+timWm;kPS z6BG+UhiuRP7lO%@9oEM}{xL5HW+nAX#<3U3toxv^9`zuElEPfxi6Uc;!FXc;a2+E% zsVMeR(uuc}b`<*y!NBt}Fy0K{M83RP%K`LH1rntG#qv@Ew<)NfyQqNa1 zDPyW`sgF_nh)X>O02L36{sn;gL-LYw9Fs2L19W8$; zmbGW-ik7IPT56vrH2n=OJTv2O;bvOJ@KG82#2i1GjIn(*lqXl&SJpkcH1&|E2BVw6JEcz{*aiZAkE8^9Z}5q#5)IL3DZ5$S4vInszgV<8Qd7uilR7m z&Fu%fiYFUQR)qAP%V|#~-4Ai?lp2oT3T8E6M{hF5h@rZ;xwNT3KmOU<+I4&;Ot zXdk}IeKM=JBfn2W#o+IjTJRTweT~NjJ{2P(kT=E|*Ul=4M#5^eQ;*H!kC`p2GVt|q zfQAvZb9$A=+)o!RPkIXT7? zlJ}aOqUpaqH?{d-e_%eo_M4JWDK+>O^Qo3TSUX^G)|c#X03ip;QN5A!piUvKL>AL1 z%7-bEE-^ziSpm(5MveIoPdmS8ZV>!M9_ytu5ADHfgbG~40WF~IoYAWrM2>Kvk>6fl5Z774tAG0g?ZyOg9gy=V!hj`kGdH7PlJh z2Z$M$ooO0t+{F~&eBSda3VhJ`I0E~`0Y}{<+)L1`Y^>+FoBEn8X+~>#wf&1>+f(VZ zzc6O$PfpvvZ}{g#B;l%Y$6*b_$MJ)P5=_A?Y>50he2aB!NDa+Oh2ubf@<0!T~5vr@Z3IT?vnnv5p;XlL86|~ zG4OKKkg<@@LX|N6wKJ&mUDQ52B^Hq-bi8zrku zpJ7Rhen-p%S#E4WOOFT@4M*j5Mf(BXe=9QZ-+YJk=?hCfgM>dJnm%Ovu3hTv#qa-x9LKWix@;z*(^S}g<&Dnk69a%{yKKEAVmPtPu<&GVGR$TMXV=>_hrXjQ?K0`6`--0)mUzVH zi|QkS8^oIKs3PBLQv_ON0~!sqM8iLwq7SQA##XM7{?t@{%&Pj*`!QJJnJtaE9EdP< zB|DAI4vLMFbViHDh@)c8?e$jv7`a8p*qQrjIm?ob3nkwV5zplcu&t?XElJR5Qzgeb zIKW=sTlAvDHixHjci?4&Tc-|2Jo252Ab@Svyi&}JH9j??bT&MEcDzzYay>LSe6aB+ zc5nJi*G`FNyJ1CDm^3+3plS zSS~i~PMM9*=NB`F0NINUbT3ZzDK9wk@I5E$y>@ClitUrQ^c|mX#9X7TM|F>!tM6JH zJ2Xy>*lJC4BtDPvoFKPEMX?*#I=0hlN>mMTpItxPJAH99TGW_cmQgEO0^l+2^2zlxh!c_Q7neJ&~=xCA`G=?IC;JDFk=}KYraF# zO!1(Cx3yzYo9&WjrG_2ub0B=&&EYZq$f{&&+=pwXZx9H-y!Vy}G$k8SBG(TD+^ELi zRFxsQ5PiK*`<3>6E-oFVHvV}$({Z^OfTkWmo{1p?uO*J%knkvkU%O{AV-ZpHDCVGM zKenK$?W=nD)EizjQd;<0Au9wMupv!(z*Cnx5Bo6iqI2DhVtOxQE}Nd!EeZ(;*f*x9 zreJ6+|Fnvu<`+y29FS3ulu*6stAX!G&2>ejP_p&pxU4}&%&7j-pDvRyKu$c0(pN!J zaJtwit5iLUX+CT@`Q>dP6ES>}8@s0u*NFaU_AcLSjZPN?=U0=lJfV<^hUS{M(I1=I?E%qWtCiC5)^b%mWsxvnWMuu z!W!2{j+BX%pm$4IvH@_N!B86dA6Fhbd}|Y!zjd5C*Y8=lr})pt{?_lp>AhqEhdB(c z*vP&tQ1)Jt#$uqpuXCn$W1^*!#(a%qg2Fy0r#BjFs=G9;gPNk zDSrOso55!n)Y6XNG3(<&g|2YvGlq*#0`<1-)G^Ny{;Mruli-2J6n84Ozeh^iq9SQSakQu>Tou50h z`$W|rm)UcizIPlIi$K}gTyHy!3dlvzxDSW-D%A$-c%4lt1hX;xo;XR3b04~>M_?>y zSZ(b-z;O?f*sOCn6g2BzdsKIw9E)vRub8(xpOh8GhjIb|?2ZKMCjt)^8GD;I1(&qE zIdal<(xwM!3?mx}>+Sk2Y6rK7%E!jDXY7^#ah}CT0#~Ho#D7hLA~FFtjX*dXR`>7R zN5Y2!ZewXekkrpe+qo+dAF9n{NS(Y(X6%k>d&keHRfPaSk;F$gVY~kJQD1FBgv^_s zv_E4U639JaVy{kAS*NtfO8!L`50bB*13O%B(}u!cq`6DK@TsHS4=UA|R7i)`GXkUd z^E@(a^EYJ(Z{;9T?0{w-`A+b6fA%cZhu`qlTTgy#`d}xQ<{ppd+b--o2rgm@3jQ@| zOXhyWQF8i6O$UX=cVWb&KR#S%7G@2^`OTYT_N4p|zCb>!o>)KP&lOZNk zT;95(7da6^^WAx9u`6qBPTL)+6(#I*cC&oY-i5t8Nk&N(bL*^QjLNOO1Zy8yij8{G|NvH|$8$_awz~qeFl%!%*Oj2vRWUT&2ejH zKB>ZIPVebHmSJEsqFm%+D334+hjjV++ZQgcK&>z({3h8LVLJ~)F=Ef2G-tLWpnM2M z!(_&%7^YRf*$jFI+4!&j=pFVw)O_`vHiP74RgjaNOqw3Rz+L?F5ue@3S3aa-+zFY+ z@+6NYT%31z;Hxt_!!E|PL|||a+OQYhJgEMGw>$M|#Pkds2U-4k9l7QQMupV?_^f~D zuIhB8Wa&WDbXd^QoM%`G?!hMGD#`Zsk=HxhiwJ*BE{tpv5o=oV`!B?46IyE(J(g}? zccTCF52MM7FTxoy-%XbhZwvb*2}woVH}rj^S20&`!B7G z(sfIPmusQCMEC3a6$?>hyKeugIj)~(@EzYu(c-d$#k-1a2>GJkL>m$`(v_O7EK+xM|OMT27Lr&f83^*uxYGr zv13{fJV1q(rZP#jR_AbT`Q7G2vEd_A0_$OiUqE3Q*{bM;IyNf6Y-wd!arieeCpOQ_L07X0`!>^S)URiDTnS z20ujPIc9Is!T;L9fs6{-DCE&;WuN=7F7)vAd0gp)Six(uOj-`(S}cT5YkuDIQ_asi z0M6A8qoYg z($O1NX+=qbef4<@%|WJ2N3=FUDZhAj^!hK6*zO+UVywX)ZvxFXQh(%g2R^cJ{r=(i z=`6k+jQUd|pWv3hKRIpB@oSE`yBq2Pj2CQpXrsQICw7`5HzbgAnl}`yH$%K>K;c3H z(B7~7btI`~8F~~EX?NQG)Fiz5RLQn#IAy6!&pAiBpQv-Km(gH{6Bz^fcroi+?&U8X zMct$AKkKXTRWcR)<0Lv3Yo>hDdyurS`4Sqhh4Ej(bGXyBzZgaPSm7+(#Le?lzx7u% zYnQ$GO*!bgqYISAF8SEqc!KgWj<}^R;Z6J#Kze+ceD$wMF-^;n>3(JUnW9(b6oyIJ z?=)yU!th?3mgLN)q{8*Yl}JBgLf-$?a$QkPW?T3tL?cCm0)nC@6e%JQ9Fb5=q6X-eB9kEK0Ooy)S%X8mtf<~zjyoD_n1x!zl>nTw5^+< zUs9#d&34D-M8F0~Bhk^zuq1)?w^n9SuQWkJq1`}6(?-=l^1TkEvPUdmMRRU=#Jgbd$UrW`58nDk1;PA z3bd3P=kDBy0uq)N`3&v0@n+2~j)#Ug?FTlW=Bw%Sbqkd`<(0Q=2n!oSy>zEL(@iSY zLffYNHU!{b5M<8Uu#d?4LYJgw{WNe&7A6TK>0FNv%Gs;K%rM)~WrO^gJ|amKG7)HXj6rGdh?xf*&g7R191*QTpgdLl>k;d@e~4b#Kv- z4LN6}x37JmZ2&j--HW7X{>0d-v5-^E)IXOat&BA| zENji_WKz#hK3RaMm5ZYSKuz1KZw&6j>8rl7uooc{^Ct;qsC7>+p)`%WBWbS^y8YD9pX!*e!WsITK|Im#^P1{<}Vvav-IR^sa~*(w%cIa;p->ZXPf z?cO4hzc$bqtq>XYakoKZ_Qq{N6-p#GrQzjW#HiprmI=3neWDxKGuZm^6#%~y%3<~R zG{`$(TJTbC$ zI9gy@?D~hY^}q>y^lQ<$-UOIt z-XI@vd)7hT?u8T~qExgCXqj4Qox~LPNdNIZc>H@#M@^zt6iH`9G`U16&7``^5KV&e z!Y-!2E2t-T8j|?sxLdYS$s)F}i!T9?ImYq|K+9()0yJDPiggO`Ipqdr?RbKox;kx- zY2f$vT_ecM&b3>~h-G4<*_3}64SC#c-J%=rHm`7ENUUi4C3WU#l2)Uf(-0x{5w#IL z#6qRZ^7~4jrHpu0)KLhBAnRoFbIi)=4}zOFxzIbM&sUd{pFI^sG|i{>T_-I$@)L8; z<0_8;mlrfg=JOuc`g+r^3wM&uEBJwhLeGcN;`z4#h0r5&c{U`bt}^d+t4vum7%vd` z>99AVO>bs#J$0*9^%%63Kvr_6F!W47MVg%mo-9~D3uqXdJ(Jd70=+`GkC~sUo{FvS z8y|4x?fk8^F_xvHUaMki;Q`w4*6K4eC9c$ww$9lK`F|t;!8d+|%klBpcr>e+XR_p| zdNYTDiw#M*aV{>tl`|rBoowgJ&ZORk(al+HpY1yIXK#-e&sPAH)SbXC?SVN>jB z8)rMzeQmO^mgpV(Kg{>ep8Q1t65;o0xhBb^tw2h&)1!%AwRy>MtE@VGHZlrQA*i@c z;nDGem)xu}B{rjrp$+woQu-B^5t{<6PdHbW2Vi^C9Y+No5t+{5k{T^&*3#mBBuKvm%bNfvX8L zl}~1FTK#EbWfw`2(f;6@1&F=QoE?9Vwq^;|Jpo&PcXgg-7~trJRX2Bf1g<-?K)?niF_Fx)}bD|Gb|CB*KPi;b6Siz2`6tS;8Sa_2|K(WZhe zrZ;cgLga1>@fYKD>ymq4bNz45IPKo*VhkJ`C}4fu=WH4$e*Xd3j@Ka8Lv;o+hlG3((fX^TY2_aeo*AE>{1~uR_Mm- z-OJK(dhn>F!OJicd{DM8F{oeu+#^jRar|8je$e^PS2W^RTmbv8*O(@{29RWoInHv? zNJ#go&{II3%**mvK&GcV{hR@)K~Gu-J3w%1mg%ah%yD(-F}45WstA{Yue{T~@iv_} z1OUK(nu9mZ|00cSdnueO55OUmCE5&SZHBTWSst=QS=t^#o1)|!6c00c=f40Uq5f9_ zV*WRPafxgv2b_&1dDDm&qmb0_(10rxGLjZUB_pp;X_o*XI_KN6>XxuG#>Sn5k6q4_ zL_kqr#r%?ry1u$=hK8a#5}$Cm*OWl QJN@e4_R11a<)b^rhX literal 0 HcmV?d00001 diff --git a/assets/apple-touch-icon.png b/assets/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..fe848f40ab7b93fd682460ff714e58b0cdf0b2db GIT binary patch literal 2082 zcmZ{lc{tRI8pnS!V`MDZx5*k!B4dom5@RrtZIFu5*oIM~aK@ghg*}AfZ?nECAH!!gm9p0@esR=iveX3AzA~k`4gdf>jC#0MRG_So8+~ z^I`x{j3{p*SPBk@0?*^^fCGB&K)ZloG)EVE*c?nqTu81+y$J#UBE61wSg$zV^2C8L zA`}5z-TgI}n65_f(yj@l%|FV!>J)}5Qu^`ao+0lTuL_+S%+{8+XJl=AYd%quCZ#ra zX#CZ@tfp6<*tW6|Pp$f}({$x}EXl9w!`Yps?>AkQz6H1cXkZm&bS9l10km z7uNN^tf~p~hCJln!0%k0u(~n1^w}m*J(XCD01Fm9VL25%^{pjcbzbmj`-M9jBWKch z8AYF~f4-)q2dYt#wmTKNh%*UuEdh^LSuQb+GJNNdnzUAl3LJ#t&V6~4wgaoooqVYg z;(&~GLGygGENBg;&R!494dt!QQ|{tr$4anSKYg`Yh8)eJe%J?K?ve^KHu6>H#?Xf1KKS@eqF`#QX(4gxbEJoP)GQJkVHqvc^M#cp*8A3e- ziF%>}W+)-P4$WE48+3SK`Tck&&K1Kp#H%FuZ;DRc>p{mllHXwvyi0}LRr%$mTOe=$ z&y~fHqfZ`NYv=SgD`_jfPm-!Wy1mq?iez)NpnjoegS;Tt1Nc3mjMGn#6?@)v2%X^M z9u;<{kW{Si=H;is<24U76BI7iF%z^ONe{g%N)G6-rK+N3sfu|^E(cIgmiD6 zndG=Q+_>1}8l0(mf^U-lMl}iqNOLN3hZUi3z(y zr@2-!dr-l;woQuey~*lnJ2!{_1Iy5Hmu0AOWsC3XC#m$^`aClTeLMK-Y(CK9t-Nh& zMyp2ir6NEqnKCg{2P6=c&Eb+()#69t^eco+hvF$Zz^j&I#FI`n{H0^7tnz)s9+Kps zye*H|z8@_SgsN?W3|=9J5EpR3GJ)RX{N#vDyEnZjTT7IQ=T;iQNzlQ)w*&o3w*j9; zb`$PI8gAMnv&ZGDoQ$XwKT4W4ZdF+SWi13G=x^Fc(jv&zar7age=^3Orig<^tI{FJ=mwxyt4E>(Ug-rrBW2ZQX;;UBKxXQF&-;fA_)pcH!PeXG)mWvd8N`I9OIHrFs2~t4Ro2_*7r3SMi-Tys zwX)bT6jL#X(5*eFk`u!6yKP)#Om)F08gqJOpU>OyThLvL9tdy+W7J=guUvUj&^@wa0B{S{LF@H(pT5-hUX56HCwyb1xlt?wWmRSXA$? zsr-i6$5)kgo9=i>Qn*GhA|9!`nGtE)shqf(YFO|y=e+HWDc_>-(-ybZ5Eg@wxJ(HF0uKYAgp1H zEH%1$FwOn?cUs8&&loBFEm??~m?jn|@-$zNaym5oOSIqs8reK3iYyQSjWRMbK$#k# zjJ%A{<|rd`H0mTuFrX$m`7{4z2)_{=783ve4gbhKNfa0^CU{+wbjj#w>5CFuL|9f3rG}0T1eCzgZ!10p@Agm2P6$F>nmOY;@CoHRBmxAdE7S?X` q(s(yE+}AzOjp&BGOF74MIt45$O4HVDO$r1r07rXQyJxokss8~+TEJ=m literal 0 HcmV?d00001 diff --git a/assets/browserconfig.xml b/assets/browserconfig.xml new file mode 100644 index 0000000..86a2869 --- /dev/null +++ b/assets/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #b91d47 + + + diff --git a/assets/css/main.css b/assets/css/main.css new file mode 100644 index 0000000..ca61c2f --- /dev/null +++ b/assets/css/main.css @@ -0,0 +1,6200 @@ +figure.highlight::before { + color: #8e908c !important; + background-color: #f7f7f7 !important; +} + +pre.lineno { + color: #8e908c !important; +} + +.highlight > pre { + color: #4d4d4c; + background-color: #f7f7f7 !important; + /* Comment */ + /* Error */ + /* Keyword */ + /* Literal */ + /* Name */ + /* Operator */ + /* Punctuation */ + /* Comment.Multiline */ + /* Comment.Preproc */ + /* Comment.Single */ + /* Comment.Special */ + /* Generic.Deleted */ + /* Generic.Emph */ + /* Generic.Heading */ + /* Generic.Inserted */ + /* Generic.Prompt */ + /* Generic.Strong */ + /* Generic.Subheading */ + /* Keyword.Constant */ + /* Keyword.Declaration */ + /* Keyword.Namespace */ + /* Keyword.Pseudo */ + /* Keyword.Reserved */ + /* Keyword.Type */ + /* Literal.Date */ + /* Literal.Number */ + /* Literal.String */ + /* Name.Attribute */ + /* Name.Builtin */ + /* Name.Class */ + /* Name.Constant */ + /* Name.Decorator */ + /* Name.Entity */ + /* Name.Exception */ + /* Name.Function */ + /* Name.Label */ + /* Name.Namespace */ + /* Name.Other */ + /* Name.Property */ + /* Name.Tag */ + /* Name.Variable */ + /* Operator.Word */ + /* Text.Whitespace */ + /* Literal.Number.Float */ + /* Literal.Number.Hex */ + /* Literal.Number.Integer */ + /* Literal.Number.Oct */ + /* Literal.String.Backtick */ + /* Literal.String.Char */ + /* Literal.String.Doc */ + /* Literal.String.Double */ + /* Literal.String.Escape */ + /* Literal.String.Heredoc */ + /* Literal.String.Interpol */ + /* Literal.String.Other */ + /* Literal.String.Regex */ + /* Literal.String.Single */ + /* Literal.String.Symbol */ + /* Name.Builtin.Pseudo */ + /* Name.Variable.Class */ + /* Name.Variable.Global */ + /* Name.Variable.Instance */ + /* Literal.Number.Integer.Long */ +} +.highlight > pre .c { + color: #8e908c; +} +.highlight > pre .err { + color: #c82829; +} +.highlight > pre .k { + color: #8959a8; +} +.highlight > pre .l { + color: #f5871f; +} +.highlight > pre .n { + color: #4d4d4c; +} +.highlight > pre .o { + color: #3e999f; +} +.highlight > pre .p { + color: #4d4d4c; +} +.highlight > pre .cm { + color: #8e908c; +} +.highlight > pre .cp { + color: #8e908c; +} +.highlight > pre .c1 { + color: #8e908c; +} +.highlight > pre .cs { + color: #8e908c; +} +.highlight > pre .gd { + color: #c82829; +} +.highlight > pre .ge { + font-style: italic; +} +.highlight > pre .gh { + font-weight: bold; + color: #4d4d4c; +} +.highlight > pre .gi { + color: #718c00; +} +.highlight > pre .gp { + font-weight: bold; + color: #8e908c; +} +.highlight > pre .gs { + font-weight: bold; +} +.highlight > pre .gu { + font-weight: bold; + color: #3e999f; +} +.highlight > pre .kc { + color: #8959a8; +} +.highlight > pre .kd { + color: #8959a8; +} +.highlight > pre .kn { + color: #3e999f; +} +.highlight > pre .kp { + color: #8959a8; +} +.highlight > pre .kr { + color: #8959a8; +} +.highlight > pre .kt { + color: #eab700; +} +.highlight > pre .ld { + color: #718c00; +} +.highlight > pre .m { + color: #f5871f; +} +.highlight > pre .s { + color: #718c00; +} +.highlight > pre .na { + color: #4271ae; +} +.highlight > pre .nb { + color: #4d4d4c; +} +.highlight > pre .nc { + color: #eab700; +} +.highlight > pre .no { + color: #c82829; +} +.highlight > pre .nd { + color: #3e999f; +} +.highlight > pre .ni { + color: #4d4d4c; +} +.highlight > pre .ne { + color: #c82829; +} +.highlight > pre .nf { + color: #4271ae; +} +.highlight > pre .nl { + color: #4d4d4c; +} +.highlight > pre .nn { + color: #eab700; +} +.highlight > pre .nx { + color: #4271ae; +} +.highlight > pre .py { + color: #4d4d4c; +} +.highlight > pre .nt { + color: #3e999f; +} +.highlight > pre .nv { + color: #c82829; +} +.highlight > pre .ow { + color: #3e999f; +} +.highlight > pre .w { + color: #4d4d4c; +} +.highlight > pre .mf { + color: #f5871f; +} +.highlight > pre .mh { + color: #f5871f; +} +.highlight > pre .mi { + color: #f5871f; +} +.highlight > pre .mo { + color: #f5871f; +} +.highlight > pre .sb { + color: #718c00; +} +.highlight > pre .sc { + color: #4d4d4c; +} +.highlight > pre .sd { + color: #8e908c; +} +.highlight > pre .s2 { + color: #718c00; +} +.highlight > pre .se { + color: #f5871f; +} +.highlight > pre .sh { + color: #718c00; +} +.highlight > pre .si { + color: #f5871f; +} +.highlight > pre .sx { + color: #718c00; +} +.highlight > pre .sr { + color: #718c00; +} +.highlight > pre .s1 { + color: #718c00; +} +.highlight > pre .ss { + color: #718c00; +} +.highlight > pre .bp { + color: #4d4d4c; +} +.highlight > pre .vc { + color: #c82829; +} +.highlight > pre .vg { + color: #c82829; +} +.highlight > pre .vi { + color: #c82829; +} +.highlight > pre .il { + color: #f5871f; +} + +/* stylelint-disable at-rule-name-space-after, at-rule-semicolon-space-before */ +.clearfix::after { + display: table; + clear: both; + content: ""; +} + +.left { + float: left; +} + +.right { + float: right; +} + +@media (min-width: 0) { + .d-none { + display: none !important; + } +} +@media (min-width: 500px) { + .d-md-none { + display: none !important; + } +} +@media (min-width: 1024px) { + .d-lg-none { + display: none !important; + } +} +@media print { + .d-print-none, .extensions { + display: none !important; + } +} + +.horizontal-rules::before { + display: block; + font-size: 1.9rem; + color: #888; + text-align: center; + letter-spacing: 1.5rem; + content: "..."; +} + +.text--light, .header--light, .hero--light, .card__image > .overlay--light { + color: #222; +} +.text--light h1, .header--light h1, .hero--light h1, .card__image > .overlay--light h1, .text--light h2, .header--light h2, .hero--light h2, .card__image > .overlay--light h2, .text--light h3, .header--light h3, .hero--light h3, .card__image > .overlay--light h3 { + color: #000; +} +.text--light h4, .header--light h4, .hero--light h4, .card__image > .overlay--light h4, .text--light h5, .header--light h5, .hero--light h5, .card__image > .overlay--light h5 { + color: #222; +} +.text--light h6, .header--light h6, .hero--light h6, .card__image > .overlay--light h6 { + color: #888; +} +.text--light a:not(.button):not(.swiper__button), .header--light a:not(.button):not(.swiper__button), .hero--light a:not(.button):not(.swiper__button), .card__image > .overlay--light a:not(.button):not(.swiper__button) { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.text--light a:not(.button):not(.swiper__button), .header--light a:not(.button):not(.swiper__button), .hero--light a:not(.button):not(.swiper__button), .card__image > .overlay--light a:not(.button):not(.swiper__button), .text--light a:not(.button):not(.swiper__button):link, .text--light a:not(.button):not(.swiper__button):visited { + text-decoration: none; +} +.root[data-is-touch=false] .text--light a:not(.button):not(.swiper__button):hover, .root[data-is-touch=false] .header--light a:not(.button):not(.swiper__button):hover, .root[data-is-touch=false] .hero--light a:not(.button):not(.swiper__button):hover, .root[data-is-touch=false] .card__image > .overlay--light a:not(.button):not(.swiper__button):hover { + text-decoration: underline; +} +.root[data-is-touch] .text--light a:not(.button):not(.swiper__button).active, .root[data-is-touch] .header--light a:not(.button):not(.swiper__button).active, .root[data-is-touch] .hero--light a:not(.button):not(.swiper__button).active, .root[data-is-touch] .card__image > .overlay--light a:not(.button):not(.swiper__button).active, .root[data-is-touch] .text--light a:not(.button):not(.swiper__button):active, .root[data-is-touch] .header--light a:not(.button):not(.swiper__button):active, .root[data-is-touch] .hero--light a:not(.button):not(.swiper__button):active, .root[data-is-touch] .card__image > .overlay--light a:not(.button):not(.swiper__button):active { + text-decoration: none; +} +.text--light a:not(.button):not(.swiper__button), .header--light a:not(.button):not(.swiper__button), .hero--light a:not(.button):not(.swiper__button), .card__image > .overlay--light a:not(.button):not(.swiper__button), .text--light a:not(.button):not(.swiper__button):link, .text--light a:not(.button):not(.swiper__button):visited { + color: #222; +} +.root[data-is-touch=false] .text--light a:not(.button):not(.swiper__button):hover, .root[data-is-touch=false] .header--light a:not(.button):not(.swiper__button):hover, .root[data-is-touch=false] .hero--light a:not(.button):not(.swiper__button):hover, .root[data-is-touch=false] .card__image > .overlay--light a:not(.button):not(.swiper__button):hover { + color: #fc4d50; +} +.root[data-is-touch] .text--light a:not(.button):not(.swiper__button).active, .root[data-is-touch] .header--light a:not(.button):not(.swiper__button).active, .root[data-is-touch] .hero--light a:not(.button):not(.swiper__button).active, .root[data-is-touch] .card__image > .overlay--light a:not(.button):not(.swiper__button).active, .root[data-is-touch] .text--light a:not(.button):not(.swiper__button):active, .root[data-is-touch] .header--light a:not(.button):not(.swiper__button):active, .root[data-is-touch] .hero--light a:not(.button):not(.swiper__button):active, .root[data-is-touch] .card__image > .overlay--light a:not(.button):not(.swiper__button):active { + color: #f80408; +} +.text--light a:not(.button):not(.swiper__button).disabled, .header--light a:not(.button):not(.swiper__button).disabled, .hero--light a:not(.button):not(.swiper__button).disabled, .card__image > .overlay--light a:not(.button):not(.swiper__button).disabled, .text--light a:not(.button):not(.swiper__button):disabled, .header--light a:not(.button):not(.swiper__button):disabled, .hero--light a:not(.button):not(.swiper__button):disabled, .card__image > .overlay--light a:not(.button):not(.swiper__button):disabled { + color: rgba(34, 34, 34, 0.2) !important; +} + +.text--dark, .header--dark, .hero--dark, .card__image > .overlay, .card__image > .overlay--dark { + color: rgba(255, 255, 255, 0.95); +} +.text--dark h1, .header--dark h1, .hero--dark h1, .card__image > .overlay h1, .card__image > .overlay--dark h1, .text--dark h2, .header--dark h2, .hero--dark h2, .card__image > .overlay h2, .card__image > .overlay--dark h2, .text--dark h3, .header--dark h3, .hero--dark h3, .card__image > .overlay h3, .card__image > .overlay--dark h3 { + color: #fff; +} +.text--dark h4, .header--dark h4, .hero--dark h4, .card__image > .overlay h4, .card__image > .overlay--dark h4, .text--dark h5, .header--dark h5, .hero--dark h5, .card__image > .overlay h5, .card__image > .overlay--dark h5 { + color: rgba(255, 255, 255, 0.95); +} +.text--dark h6, .header--dark h6, .hero--dark h6, .card__image > .overlay h6, .card__image > .overlay--dark h6 { + color: rgba(255, 255, 255, 0.85); +} +.text--dark a:not(.button):not(.swiper__button), .header--dark a:not(.button):not(.swiper__button), .hero--dark a:not(.button):not(.swiper__button), .card__image > .overlay a:not(.button):not(.swiper__button), .card__image > .overlay--dark a:not(.button):not(.swiper__button) { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.text--dark a:not(.button):not(.swiper__button), .header--dark a:not(.button):not(.swiper__button), .hero--dark a:not(.button):not(.swiper__button), .card__image > .overlay a:not(.button):not(.swiper__button), .card__image > .overlay--dark a:not(.button):not(.swiper__button), .text--dark a:not(.button):not(.swiper__button):link, .text--dark a:not(.button):not(.swiper__button):visited { + text-decoration: none; +} +.root[data-is-touch=false] .text--dark a:not(.button):not(.swiper__button):hover, .root[data-is-touch=false] .header--dark a:not(.button):not(.swiper__button):hover, .root[data-is-touch=false] .hero--dark a:not(.button):not(.swiper__button):hover, .root[data-is-touch=false] .card__image > .overlay a:not(.button):not(.swiper__button):hover, .root[data-is-touch=false] .card__image > .overlay--dark a:not(.button):not(.swiper__button):hover { + text-decoration: underline; +} +.root[data-is-touch] .text--dark a:not(.button):not(.swiper__button).active, .root[data-is-touch] .header--dark a:not(.button):not(.swiper__button).active, .root[data-is-touch] .hero--dark a:not(.button):not(.swiper__button).active, .root[data-is-touch] .card__image > .overlay a:not(.button):not(.swiper__button).active, .root[data-is-touch] .card__image > .overlay--dark a:not(.button):not(.swiper__button).active, .root[data-is-touch] .text--dark a:not(.button):not(.swiper__button):active, .root[data-is-touch] .header--dark a:not(.button):not(.swiper__button):active, .root[data-is-touch] .hero--dark a:not(.button):not(.swiper__button):active, .root[data-is-touch] .card__image > .overlay a:not(.button):not(.swiper__button):active, .root[data-is-touch] .card__image > .overlay--dark a:not(.button):not(.swiper__button):active { + text-decoration: none; +} +.text--dark a:not(.button):not(.swiper__button), .header--dark a:not(.button):not(.swiper__button), .hero--dark a:not(.button):not(.swiper__button), .card__image > .overlay a:not(.button):not(.swiper__button), .card__image > .overlay--dark a:not(.button):not(.swiper__button), .text--dark a:not(.button):not(.swiper__button):link, .text--dark a:not(.button):not(.swiper__button):visited { + color: rgba(255, 255, 255, 0.95); +} +.root[data-is-touch=false] .text--dark a:not(.button):not(.swiper__button):hover, .root[data-is-touch=false] .header--dark a:not(.button):not(.swiper__button):hover, .root[data-is-touch=false] .hero--dark a:not(.button):not(.swiper__button):hover, .root[data-is-touch=false] .card__image > .overlay a:not(.button):not(.swiper__button):hover, .root[data-is-touch=false] .card__image > .overlay--dark a:not(.button):not(.swiper__button):hover { + color: #fc4d50; +} +.root[data-is-touch] .text--dark a:not(.button):not(.swiper__button).active, .root[data-is-touch] .header--dark a:not(.button):not(.swiper__button).active, .root[data-is-touch] .hero--dark a:not(.button):not(.swiper__button).active, .root[data-is-touch] .card__image > .overlay a:not(.button):not(.swiper__button).active, .root[data-is-touch] .card__image > .overlay--dark a:not(.button):not(.swiper__button).active, .root[data-is-touch] .text--dark a:not(.button):not(.swiper__button):active, .root[data-is-touch] .header--dark a:not(.button):not(.swiper__button):active, .root[data-is-touch] .hero--dark a:not(.button):not(.swiper__button):active, .root[data-is-touch] .card__image > .overlay a:not(.button):not(.swiper__button):active, .root[data-is-touch] .card__image > .overlay--dark a:not(.button):not(.swiper__button):active { + color: #f80408; +} +.text--dark a:not(.button):not(.swiper__button).disabled, .header--dark a:not(.button):not(.swiper__button).disabled, .hero--dark a:not(.button):not(.swiper__button).disabled, .card__image > .overlay a:not(.button):not(.swiper__button).disabled, .card__image > .overlay--dark a:not(.button):not(.swiper__button).disabled, .text--dark a:not(.button):not(.swiper__button):disabled, .header--dark a:not(.button):not(.swiper__button):disabled, .hero--dark a:not(.button):not(.swiper__button):disabled, .card__image > .overlay a:not(.button):not(.swiper__button):disabled, .card__image > .overlay--dark a:not(.button):not(.swiper__button):disabled { + color: rgba(255, 255, 255, 0.2) !important; +} + +.of-auto { + overflow: auto; + -webkit-overflow-scrolling: touch; +} + +.of-hidden { + overflow: hidden; +} + +.box-shadow-1 { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.23), 0 1px 3px rgba(0, 0, 0, 0.08), 0 6px 12px rgba(0, 0, 0, 0.02); +} + +.box-shadow-2 { + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.23), 0 2px 6px rgba(0, 0, 0, 0.08), 0 12px 24px rgba(0, 0, 0, 0.02); +} + +.mt-0 { + margin-top: 0; +} + +.mt-1 { + margin-top: 0.25rem; +} + +.mt-2 { + margin-top: 0.5rem; +} + +.mt-3 { + margin-top: 1rem; +} + +.mt-4 { + margin-top: 1.5rem; +} + +.mt-5 { + margin-top: 3rem; +} + +.mb-0 { + margin-bottom: 0; +} + +.mb-1 { + margin-bottom: 0.25rem; +} + +.mb-2 { + margin-bottom: 0.5rem; +} + +.mb-3 { + margin-bottom: 1rem; +} + +.mb-4 { + margin-bottom: 1.5rem; +} + +.mb-5 { + margin-bottom: 3rem; +} + +.ml-0 { + margin-left: 0; +} + +.ml-1 { + margin-left: 0.25rem; +} + +.ml-2 { + margin-left: 0.5rem; +} + +.ml-3 { + margin-left: 1rem; +} + +.ml-4 { + margin-left: 1.5rem; +} + +.ml-5 { + margin-left: 3rem; +} + +.mr-0 { + margin-right: 0; +} + +.mr-1 { + margin-right: 0.25rem; +} + +.mr-2 { + margin-right: 0.5rem; +} + +.mr-3 { + margin-right: 1rem; +} + +.mr-4 { + margin-right: 1.5rem; +} + +.mr-5 { + margin-right: 3rem; +} + +.mx-0 { + margin-left: 0; + margin-right: 0; +} + +.mx-1 { + margin-left: 0.25rem; + margin-right: 0.25rem; +} + +.mx-2 { + margin-left: 0.5rem; + margin-right: 0.5rem; +} + +.mx-3 { + margin-left: 1rem; + margin-right: 1rem; +} + +.mx-4 { + margin-left: 1.5rem; + margin-right: 1.5rem; +} + +.mx-5 { + margin-left: 3rem; + margin-right: 3rem; +} + +.my-0 { + margin-top: 0; + margin-bottom: 0; +} + +.my-1 { + margin-top: 0.25rem; + margin-bottom: 0.25rem; +} + +.my-2 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +.my-3 { + margin-top: 1rem; + margin-bottom: 1rem; +} + +.my-4 { + margin-top: 1.5rem; + margin-bottom: 1.5rem; +} + +.my-5 { + margin-top: 3rem; + margin-bottom: 3rem; +} + +.m-0 { + margin: 0; +} + +.m-1 { + margin: 0.25rem; +} + +.m-2 { + margin: 0.5rem; +} + +.m-3 { + margin: 1rem; +} + +.m-4 { + margin: 1.5rem; +} + +.m-5 { + margin: 3rem; +} + +.pt-0 { + padding-top: 0; +} + +.pt-1 { + padding-top: 0.25rem; +} + +.pt-2 { + padding-top: 0.5rem; +} + +.pt-3 { + padding-top: 1rem; +} + +.pt-4 { + padding-top: 1.5rem; +} + +.pt-5 { + padding-top: 3rem; +} + +.pb-0 { + padding-bottom: 0; +} + +.pb-1 { + padding-bottom: 0.25rem; +} + +.pb-2 { + padding-bottom: 0.5rem; +} + +.pb-3 { + padding-bottom: 1rem; +} + +.pb-4 { + padding-bottom: 1.5rem; +} + +.pb-5 { + padding-bottom: 3rem; +} + +.pl-0 { + padding-left: 0; +} + +.pl-1 { + padding-left: 0.25rem; +} + +.pl-2 { + padding-left: 0.5rem; +} + +.pl-3 { + padding-left: 1rem; +} + +.pl-4 { + padding-left: 1.5rem; +} + +.pl-5 { + padding-left: 3rem; +} + +.pr-0 { + padding-right: 0; +} + +.pr-1 { + padding-right: 0.25rem; +} + +.pr-2 { + padding-right: 0.5rem; +} + +.pr-3 { + padding-right: 1rem; +} + +.pr-4 { + padding-right: 1.5rem; +} + +.pr-5 { + padding-right: 3rem; +} + +.px-0 { + padding-left: 0; + padding-right: 0; +} + +.px-1 { + padding-left: 0.25rem; + padding-right: 0.25rem; +} + +.px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} + +.px-3 { + padding-left: 1rem; + padding-right: 1rem; +} + +.px-4 { + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.px-5 { + padding-left: 3rem; + padding-right: 3rem; +} + +.py-0 { + padding-top: 0; + padding-bottom: 0; +} + +.py-1 { + padding-top: 0.25rem; + padding-bottom: 0.25rem; +} + +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.py-3 { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.py-4 { + padding-top: 1.5rem; + padding-bottom: 1.5rem; +} + +.py-5 { + padding-top: 3rem; + padding-bottom: 3rem; +} + +.p-0 { + padding: 0; +} + +.p-1 { + padding: 0.25rem; +} + +.p-2 { + padding: 0.5rem; +} + +.p-3 { + padding: 1rem; +} + +.p-4 { + padding: 1.5rem; +} + +.p-5 { + padding: 3rem; +} + +.mt-auto { + margin-top: auto; +} + +.mb-auto { + margin-bottom: auto; +} + +.ml-auto { + margin-left: auto; +} + +.mr-auto { + margin-right: auto; +} + +.mx-auto { + margin-left: auto; + margin-right: auto; +} + +.my-auto { + margin-top: auto; + margin-bottom: auto; +} + +.m-auto { + margin: auto; +} + +.grid-container { + overflow: hidden; +} + +.cell { + min-width: 0; +} + +.grid { + display: -webkit-box; + display: -webkit-flex; + display: -moz-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-wrap: wrap; + -moz-flex-wrap: wrap; + -ms-flex-wrap: wrap; + flex-wrap: wrap; +} +@media (min-width: 0) { + .grid > .cell--1 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 8.3333333333%; + } +} +@media (min-width: 0) { + .grid > .cell--2 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 16.6666666667%; + } +} +@media (min-width: 0) { + .grid > .cell--3 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 25%; + } +} +@media (min-width: 0) { + .grid > .cell--4 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 33.3333333333%; + } +} +@media (min-width: 0) { + .grid > .cell--5 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 41.6666666667%; + } +} +@media (min-width: 0) { + .grid > .cell--6 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 50%; + } +} +@media (min-width: 0) { + .grid > .cell--7 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 58.3333333333%; + } +} +@media (min-width: 0) { + .grid > .cell--8 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 66.6666666667%; + } +} +@media (min-width: 0) { + .grid > .cell--9 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 75%; + } +} +@media (min-width: 0) { + .grid > .cell--10 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 83.3333333333%; + } +} +@media (min-width: 0) { + .grid > .cell--11 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 91.6666666667%; + } +} +@media (min-width: 0) { + .grid > .cell--12 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 100%; + } +} +@media (min-width: 0) { + .grid > .cell--auto { + -webkit-box-flex: 1; + -webkit-flex: 1 1 0; + -moz-box-flex: 1; + -moz-flex: 1 1 0; + -ms-flex: 1 1 0; + flex: 1 1 0; + width: auto; + } +} +@media (min-width: 0) { + .grid > .cell--shrink { + -webkit-box-flex: 0; + -webkit-flex: 0 0 auto; + -moz-box-flex: 0; + -moz-flex: 0 0 auto; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + } +} +@media (min-width: 0) { + .grid > .cell--stretch { + -webkit-box-flex: 1; + -webkit-flex: 1; + -moz-box-flex: 1; + -moz-flex: 1; + -ms-flex: 1; + flex: 1; + } +} +@media (min-width: 500px) { + .grid > .cell--md-1 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 8.3333333333%; + } +} +@media (min-width: 500px) { + .grid > .cell--md-2 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 16.6666666667%; + } +} +@media (min-width: 500px) { + .grid > .cell--md-3 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 25%; + } +} +@media (min-width: 500px) { + .grid > .cell--md-4 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 33.3333333333%; + } +} +@media (min-width: 500px) { + .grid > .cell--md-5 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 41.6666666667%; + } +} +@media (min-width: 500px) { + .grid > .cell--md-6 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 50%; + } +} +@media (min-width: 500px) { + .grid > .cell--md-7 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 58.3333333333%; + } +} +@media (min-width: 500px) { + .grid > .cell--md-8 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 66.6666666667%; + } +} +@media (min-width: 500px) { + .grid > .cell--md-9 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 75%; + } +} +@media (min-width: 500px) { + .grid > .cell--md-10 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 83.3333333333%; + } +} +@media (min-width: 500px) { + .grid > .cell--md-11 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 91.6666666667%; + } +} +@media (min-width: 500px) { + .grid > .cell--md-12 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 100%; + } +} +@media (min-width: 500px) { + .grid > .cell--md-auto { + -webkit-box-flex: 1; + -webkit-flex: 1 1 0; + -moz-box-flex: 1; + -moz-flex: 1 1 0; + -ms-flex: 1 1 0; + flex: 1 1 0; + width: auto; + } +} +@media (min-width: 500px) { + .grid > .cell--md-shrink { + -webkit-box-flex: 0; + -webkit-flex: 0 0 auto; + -moz-box-flex: 0; + -moz-flex: 0 0 auto; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + } +} +@media (min-width: 500px) { + .grid > .cell--md-stretch { + -webkit-box-flex: 1; + -webkit-flex: 1; + -moz-box-flex: 1; + -moz-flex: 1; + -ms-flex: 1; + flex: 1; + } +} +@media (min-width: 1024px) { + .grid > .cell--lg-1 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 8.3333333333%; + } +} +@media (min-width: 1024px) { + .grid > .cell--lg-2 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 16.6666666667%; + } +} +@media (min-width: 1024px) { + .grid > .cell--lg-3 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 25%; + } +} +@media (min-width: 1024px) { + .grid > .cell--lg-4 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 33.3333333333%; + } +} +@media (min-width: 1024px) { + .grid > .cell--lg-5 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 41.6666666667%; + } +} +@media (min-width: 1024px) { + .grid > .cell--lg-6 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 50%; + } +} +@media (min-width: 1024px) { + .grid > .cell--lg-7 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 58.3333333333%; + } +} +@media (min-width: 1024px) { + .grid > .cell--lg-8 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 66.6666666667%; + } +} +@media (min-width: 1024px) { + .grid > .cell--lg-9 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 75%; + } +} +@media (min-width: 1024px) { + .grid > .cell--lg-10 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 83.3333333333%; + } +} +@media (min-width: 1024px) { + .grid > .cell--lg-11 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 91.6666666667%; + } +} +@media (min-width: 1024px) { + .grid > .cell--lg-12 { + -webkit-box-flex: none; + -webkit-flex: none; + -moz-box-flex: none; + -moz-flex: none; + -ms-flex: none; + flex: none; + width: 100%; + } +} +@media (min-width: 1024px) { + .grid > .cell--lg-auto { + -webkit-box-flex: 1; + -webkit-flex: 1 1 0; + -moz-box-flex: 1; + -moz-flex: 1 1 0; + -ms-flex: 1 1 0; + flex: 1 1 0; + width: auto; + } +} +@media (min-width: 1024px) { + .grid > .cell--lg-shrink { + -webkit-box-flex: 0; + -webkit-flex: 0 0 auto; + -moz-box-flex: 0; + -moz-flex: 0 0 auto; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + } +} +@media (min-width: 1024px) { + .grid > .cell--lg-stretch { + -webkit-box-flex: 1; + -webkit-flex: 1; + -moz-box-flex: 1; + -moz-flex: 1; + -ms-flex: 1; + flex: 1; + } +} + +.grid--reverse { + flex-direction: row-reverse; +} + +.grid--px-0 { + margin-left: 0; + margin-right: 0; +} +.grid--px-0 .cell { + padding-left: 0; + padding-right: 0; +} + +.grid--px-1 { + margin-left: -0.25rem; + margin-right: -0.25rem; +} +.grid--px-1 .cell { + padding-left: 0.25rem; + padding-right: 0.25rem; +} + +.grid--px-2 { + margin-left: -0.5rem; + margin-right: -0.5rem; +} +.grid--px-2 .cell { + padding-left: 0.5rem; + padding-right: 0.5rem; +} + +.grid--px-3 { + margin-left: -1rem; + margin-right: -1rem; +} +.grid--px-3 .cell { + padding-left: 1rem; + padding-right: 1rem; +} + +.grid--px-4 { + margin-left: -1.5rem; + margin-right: -1.5rem; +} +.grid--px-4 .cell { + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.grid--px-5 { + margin-left: -3rem; + margin-right: -3rem; +} +.grid--px-5 .cell { + padding-left: 3rem; + padding-right: 3rem; +} + +.grid--py-0 { + margin-top: 0; + margin-bottom: 0; +} +.grid--py-0 .cell { + padding-top: 0; + padding-bottom: 0; +} + +.grid--py-1 { + margin-top: -0.25rem; + margin-bottom: -0.25rem; +} +.grid--py-1 .cell { + padding-top: 0.25rem; + padding-bottom: 0.25rem; +} + +.grid--py-2 { + margin-top: -0.5rem; + margin-bottom: -0.5rem; +} +.grid--py-2 .cell { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.grid--py-3 { + margin-top: -1rem; + margin-bottom: -1rem; +} +.grid--py-3 .cell { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.grid--py-4 { + margin-top: -1.5rem; + margin-bottom: -1.5rem; +} +.grid--py-4 .cell { + padding-top: 1.5rem; + padding-bottom: 1.5rem; +} + +.grid--py-5 { + margin-top: -3rem; + margin-bottom: -3rem; +} +.grid--py-5 .cell { + padding-top: 3rem; + padding-bottom: 3rem; +} + +.grid--p-0 { + margin: 0; +} +.grid--p-0 .cell { + padding: 0; +} + +.grid--p-1 { + margin: -0.25rem; +} +.grid--p-1 .cell { + padding: 0.25rem; +} + +.grid--p-2 { + margin: -0.5rem; +} +.grid--p-2 .cell { + padding: 0.5rem; +} + +.grid--p-3 { + margin: -1rem; +} +.grid--p-3 .cell { + padding: 1rem; +} + +.grid--p-4 { + margin: -1.5rem; +} +.grid--p-4 .cell { + padding: 1.5rem; +} + +.grid--p-5 { + margin: -3rem; +} +.grid--p-5 .cell { + padding: 3rem; +} + +/* stylelint-enable */ +*, +::before, +::after { + box-sizing: border-box; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +/** + * 1. Prevent adjustments of font size after orientation changes in iOS. + **/ +html { + font-size: 16px; + -webkit-text-size-adjust: 100%; /* 1 */ +} +@media print { + html { + font-size: 14px; + } +} + +body { + padding: 0; + margin: 0; + font: 400 1rem/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; +} +body ::-moz-selection { + background: rgba(252, 77, 80, 0.5); +} +body ::-webkit-selection { + background: rgba(252, 77, 80, 0.5); +} +body ::selection { + background: rgba(252, 77, 80, 0.5); +} + +h1, +h2, +h3, +h4, +h5, +h6, +p, +hr, +blockquote, +figure, +pre, +.highlighter-rouge, +ul, +ol, +dl, +table, +.footnotes { + padding: 0; + margin: 0.5rem 0; +} + +input, textarea, select, button { + font: 400 1rem/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + color: #222; +} + +h1, +h2, +h3, +h4, +h5, +h6, +strong { + font-weight: 700; +} + +h1 { + font-size: 2.5rem; + color: #000; +} +@media (max-width: 499px) { + h1 { + font-size: 2rem; + } +} + +h2 { + font-size: 1.9rem; + color: #000; +} +@media (max-width: 499px) { + h2 { + font-size: 1.5rem; + } +} + +h3 { + font-size: 1.5rem; + color: #000; +} +@media (max-width: 499px) { + h3 { + font-size: 1.35rem; + } +} + +h4 { + font-size: 1.2rem; + color: #222; +} +@media (max-width: 499px) { + h4 { + font-size: 1.15rem; + } +} + +h5 { + font-size: 1rem; + color: #222; +} +@media (max-width: 499px) { + h5 { + font-size: 1rem; + } +} + +h6 { + font-size: 1rem; + color: #888; +} +@media (max-width: 499px) { + h6 { + font-size: 1rem; + } +} + +a { + font-weight: 700; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +a, a:link, a:visited { + text-decoration: none; +} +.root[data-is-touch=false] a:hover { + text-decoration: underline; +} +.root[data-is-touch] a.active, .root[data-is-touch] a:active { + text-decoration: none; +} +a, a:link, a:visited { + color: #fc4d50; +} +.root[data-is-touch=false] a:hover { + color: #fb070b; +} +.root[data-is-touch] a.active, .root[data-is-touch] a:active { + color: #b20306; +} +a.disabled, a:disabled { + color: rgba(252, 77, 80, 0.2) !important; +} + +pre, code { + font-family: Menlo, Monaco, Consolas, Andale Mono, lucida console, Courier New, monospace; +} + +code { + font-size: 0.7rem; + line-height: 1.4; +} + +figure > img { + display: block; +} + +figcaption { + font-size: 0.85rem; +} + +button { + padding: 0; + margin: 0; + font-size: 1rem; + cursor: pointer; + background-color: transparent; + border-width: 0; + outline: none; +} + +input::-ms-clear { + display: none; +} +input:focus { + outline: none; +} + +.mermaidTooltip { + display: none; +} + +@media print { + a, a:link, a:visited { + text-decoration: underline; + } + .root[data-is-touch=false] a:hover { + text-decoration: underline; + } + .root[data-is-touch] a.active, .root[data-is-touch] a:active { + text-decoration: underline; + } + img, + tr, + pre, + blockquote { + page-break-inside: avoid; + } +} +.button, .swiper__button { + display: inline-block; + font-weight: 700; + line-height: 1 !important; + text-decoration: none !important; + cursor: pointer; + outline: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.button svg, .swiper__button svg { + width: 1rem; + height: 1rem; +} +.button.disabled, .disabled.swiper__button, .button:disabled, .swiper__button:disabled { + cursor: not-allowed; +} + +.button--primary { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--primary svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--primary, .button--primary:link, .button--primary:visited { + color: #fff; + background-color: #fc4d50; +} +.button--primary svg path, .button--primary:link svg path, .button--primary:visited svg path { + fill: #fff; +} +.root[data-is-touch=false] .button--primary:hover { + color: #fff; + background-color: #fb070b; +} +.root[data-is-touch=false] .button--primary:hover svg path { + fill: #fff; +} +.root[data-is-touch] .button--primary.active, .root[data-is-touch] .button--primary:active { + color: #fff; + background-color: #b20306; +} +.root[data-is-touch] .button--primary.active svg path, .root[data-is-touch] .button--primary:active svg path { + fill: #fff; +} +.root[data-is-touch] .button--primary.focus { + color: default; + background-color: #fb070b; + box-shadow: 0 0 0 2px rgba(251, 7, 11, 0.4); +} +.root[data-is-touch] .button--primary.focus svg path { + fill: default; +} +.button--primary.disabled, .button--primary:disabled { + color: rgba(255, 255, 255, 0.2) !important; + background-color: #fc4d50 !important; +} +.button--primary.disabled svg path, .button--primary:disabled svg path { + fill: rgba(255, 255, 255, 0.2) !important; +} + +.button--secondary { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--secondary svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--secondary, .button--secondary:link, .button--secondary:visited { + color: #333; + background-color: #f2f2f2; +} +.button--secondary svg path, .button--secondary:link svg path, .button--secondary:visited svg path { + fill: #333; +} +.root[data-is-touch=false] .button--secondary:hover { + color: #333; + background-color: #cecece; +} +.root[data-is-touch=false] .button--secondary:hover svg path { + fill: #333; +} +.root[data-is-touch] .button--secondary.active, .root[data-is-touch] .button--secondary:active { + color: #333; + background-color: #a8a8a8; +} +.root[data-is-touch] .button--secondary.active svg path, .root[data-is-touch] .button--secondary:active svg path { + fill: #333; +} +.root[data-is-touch] .button--secondary.focus { + color: default; + background-color: #cecece; + box-shadow: 0 0 0 2px rgba(206, 206, 206, 0.4); +} +.root[data-is-touch] .button--secondary.focus svg path { + fill: default; +} +.button--secondary.disabled, .button--secondary:disabled { + color: rgba(51, 51, 51, 0.2) !important; + background-color: #f2f2f2 !important; +} +.button--secondary.disabled svg path, .button--secondary:disabled svg path { + fill: rgba(51, 51, 51, 0.2) !important; +} + +.button--success { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--success svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--success, .button--success:link, .button--success:visited { + color: #fff; + background-color: #52c41a; +} +.button--success svg path, .button--success:link svg path, .button--success:visited svg path { + fill: #fff; +} +.root[data-is-touch=false] .button--success:hover { + color: #fff; + background-color: #388512; +} +.root[data-is-touch=false] .button--success:hover svg path { + fill: #fff; +} +.root[data-is-touch] .button--success.active, .root[data-is-touch] .button--success:active { + color: #fff; + background-color: #1b4109; +} +.root[data-is-touch] .button--success.active svg path, .root[data-is-touch] .button--success:active svg path { + fill: #fff; +} +.root[data-is-touch] .button--success.focus { + color: default; + background-color: #388512; + box-shadow: 0 0 0 2px rgba(56, 133, 18, 0.4); +} +.root[data-is-touch] .button--success.focus svg path { + fill: default; +} +.button--success.disabled, .button--success:disabled { + color: rgba(255, 255, 255, 0.2) !important; + background-color: #52c41a !important; +} +.button--success.disabled svg path, .button--success:disabled svg path { + fill: rgba(255, 255, 255, 0.2) !important; +} + +.button--info { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--info svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--info, .button--info:link, .button--info:visited { + color: #fff; + background-color: #1890ff; +} +.button--info svg path, .button--info:link svg path, .button--info:visited svg path { + fill: #fff; +} +.root[data-is-touch=false] .button--info:hover { + color: #fff; + background-color: #006cd0; +} +.root[data-is-touch=false] .button--info:hover svg path { + fill: #fff; +} +.root[data-is-touch] .button--info.active, .root[data-is-touch] .button--info:active { + color: #fff; + background-color: #004483; +} +.root[data-is-touch] .button--info.active svg path, .root[data-is-touch] .button--info:active svg path { + fill: #fff; +} +.root[data-is-touch] .button--info.focus { + color: default; + background-color: #006cd0; + box-shadow: 0 0 0 2px rgba(0, 108, 208, 0.4); +} +.root[data-is-touch] .button--info.focus svg path { + fill: default; +} +.button--info.disabled, .button--info:disabled { + color: rgba(255, 255, 255, 0.2) !important; + background-color: #1890ff !important; +} +.button--info.disabled svg path, .button--info:disabled svg path { + fill: rgba(255, 255, 255, 0.2) !important; +} + +.button--warning { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--warning svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--warning, .button--warning:link, .button--warning:visited { + color: #fff; + background-color: #fa8c16; +} +.button--warning svg path, .button--warning:link svg path, .button--warning:visited svg path { + fill: #fff; +} +.root[data-is-touch=false] .button--warning:hover { + color: #fff; + background-color: #c46804; +} +.root[data-is-touch=false] .button--warning:hover svg path { + fill: #fff; +} +.root[data-is-touch] .button--warning.active, .root[data-is-touch] .button--warning:active { + color: #fff; + background-color: #794003; +} +.root[data-is-touch] .button--warning.active svg path, .root[data-is-touch] .button--warning:active svg path { + fill: #fff; +} +.root[data-is-touch] .button--warning.focus { + color: default; + background-color: #c46804; + box-shadow: 0 0 0 2px rgba(196, 104, 4, 0.4); +} +.root[data-is-touch] .button--warning.focus svg path { + fill: default; +} +.button--warning.disabled, .button--warning:disabled { + color: rgba(255, 255, 255, 0.2) !important; + background-color: #fa8c16 !important; +} +.button--warning.disabled svg path, .button--warning:disabled svg path { + fill: rgba(255, 255, 255, 0.2) !important; +} + +.button--error { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--error svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--error, .button--error:link, .button--error:visited { + color: #fff; + background-color: #f5222d; +} +.button--error svg path, .button--error:link svg path, .button--error:visited svg path { + fill: #fff; +} +.root[data-is-touch=false] .button--error:hover { + color: #fff; + background-color: #c70913; +} +.root[data-is-touch=false] .button--error:hover svg path { + fill: #fff; +} +.root[data-is-touch] .button--error.active, .root[data-is-touch] .button--error:active { + color: #fff; + background-color: #7d060c; +} +.root[data-is-touch] .button--error.active svg path, .root[data-is-touch] .button--error:active svg path { + fill: #fff; +} +.root[data-is-touch] .button--error.focus { + color: default; + background-color: #c70913; + box-shadow: 0 0 0 2px rgba(199, 9, 19, 0.4); +} +.root[data-is-touch] .button--error.focus svg path { + fill: default; +} +.button--error.disabled, .button--error:disabled { + color: rgba(255, 255, 255, 0.2) !important; + background-color: #f5222d !important; +} +.button--error.disabled svg path, .button--error:disabled svg path { + fill: rgba(255, 255, 255, 0.2) !important; +} + +.button--theme-light { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--theme-light svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--theme-light, .button--theme-light:link, .button--theme-light:visited { + color: rgba(255, 255, 255, 0.95); + background-color: rgba(0, 0, 0, 0.9); +} +.button--theme-light svg path, .button--theme-light:link svg path, .button--theme-light:visited svg path { + fill: rgba(255, 255, 255, 0.95); +} +.root[data-is-touch=false] .button--theme-light:hover { + color: rgba(255, 255, 255, 0.95); + background-color: rgba(46, 46, 46, 0.9); +} +.root[data-is-touch=false] .button--theme-light:hover svg path { + fill: rgba(255, 255, 255, 0.95); +} +.root[data-is-touch] .button--theme-light.active, .root[data-is-touch] .button--theme-light:active { + color: rgba(255, 255, 255, 0.95); + background-color: rgba(87, 87, 87, 0.9); +} +.root[data-is-touch] .button--theme-light.active svg path, .root[data-is-touch] .button--theme-light:active svg path { + fill: rgba(255, 255, 255, 0.95); +} +.root[data-is-touch] .button--theme-light.focus { + color: default; + background-color: rgba(46, 46, 46, 0.9); + box-shadow: 0 0 0 2px rgba(46, 46, 46, 0.4); +} +.root[data-is-touch] .button--theme-light.focus svg path { + fill: default; +} +.button--theme-light.disabled, .button--theme-light:disabled { + color: rgba(255, 255, 255, 0.4) !important; + background-color: rgba(0, 0, 0, 0.9) !important; +} +.button--theme-light.disabled svg path, .button--theme-light:disabled svg path { + fill: rgba(255, 255, 255, 0.4) !important; +} + +.button--theme-dark { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--theme-dark svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--theme-dark, .button--theme-dark:link, .button--theme-dark:visited { + color: #222; + background-color: rgba(255, 255, 255, 0.9); +} +.button--theme-dark svg path, .button--theme-dark:link svg path, .button--theme-dark:visited svg path { + fill: #222; +} +.root[data-is-touch=false] .button--theme-dark:hover { + color: #222; + background-color: rgba(219, 219, 219, 0.9); +} +.root[data-is-touch=false] .button--theme-dark:hover svg path { + fill: #222; +} +.root[data-is-touch] .button--theme-dark.active, .root[data-is-touch] .button--theme-dark:active { + color: #222; + background-color: rgba(181, 181, 181, 0.9); +} +.root[data-is-touch] .button--theme-dark.active svg path, .root[data-is-touch] .button--theme-dark:active svg path { + fill: #222; +} +.root[data-is-touch] .button--theme-dark.focus { + color: default; + background-color: rgba(219, 219, 219, 0.9); + box-shadow: 0 0 0 2px rgba(219, 219, 219, 0.4); +} +.root[data-is-touch] .button--theme-dark.focus svg path { + fill: default; +} +.button--theme-dark.disabled, .button--theme-dark:disabled { + color: rgba(34, 34, 34, 0.2) !important; + background-color: rgba(255, 255, 255, 0.9) !important; +} +.button--theme-dark.disabled svg path, .button--theme-dark:disabled svg path { + fill: rgba(34, 34, 34, 0.2) !important; +} + +.button--outline-primary { + color: #fc4d50; + border: 1px solid #fc4d50; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--outline-primary svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--outline-primary, .button--outline-primary:link, .button--outline-primary:visited { + color: #fc4d50; + background-color: transparent; +} +.button--outline-primary svg path, .button--outline-primary:link svg path, .button--outline-primary:visited svg path { + fill: #fc4d50; +} +.root[data-is-touch=false] .button--outline-primary:hover { + color: #fff; + background-color: #fc4d50; +} +.root[data-is-touch=false] .button--outline-primary:hover svg path { + fill: #fff; +} +.root[data-is-touch] .button--outline-primary.active, .root[data-is-touch] .button--outline-primary:active { + color: #fff; + background-color: #f80408; +} +.root[data-is-touch] .button--outline-primary.active svg path, .root[data-is-touch] .button--outline-primary:active svg path { + fill: #fff; +} +.root[data-is-touch] .button--outline-primary.focus { + color: default; + background-color: #fc4d50; + box-shadow: 0 0 0 2px rgba(252, 77, 80, 0.4); +} +.root[data-is-touch] .button--outline-primary.focus svg path { + fill: default; +} +.button--outline-primary.disabled, .button--outline-primary:disabled { + color: rgba(252, 77, 80, 0.2) !important; + background-color: transparent !important; +} +.button--outline-primary.disabled svg path, .button--outline-primary:disabled svg path { + fill: rgba(252, 77, 80, 0.2) !important; +} + +.button--outline-secondary { + color: #f2f2f2; + border: 1px solid #f2f2f2; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--outline-secondary svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--outline-secondary, .button--outline-secondary:link, .button--outline-secondary:visited { + color: #f2f2f2; + background-color: transparent; +} +.button--outline-secondary svg path, .button--outline-secondary:link svg path, .button--outline-secondary:visited svg path { + fill: #f2f2f2; +} +.root[data-is-touch=false] .button--outline-secondary:hover { + color: #333; + background-color: #f2f2f2; +} +.root[data-is-touch=false] .button--outline-secondary:hover svg path { + fill: #333; +} +.root[data-is-touch] .button--outline-secondary.active, .root[data-is-touch] .button--outline-secondary:active { + color: #333; + background-color: #cccccc; +} +.root[data-is-touch] .button--outline-secondary.active svg path, .root[data-is-touch] .button--outline-secondary:active svg path { + fill: #333; +} +.root[data-is-touch] .button--outline-secondary.focus { + color: default; + background-color: #f2f2f2; + box-shadow: 0 0 0 2px rgba(242, 242, 242, 0.4); +} +.root[data-is-touch] .button--outline-secondary.focus svg path { + fill: default; +} +.button--outline-secondary.disabled, .button--outline-secondary:disabled { + color: rgba(242, 242, 242, 0.2) !important; + background-color: transparent !important; +} +.button--outline-secondary.disabled svg path, .button--outline-secondary:disabled svg path { + fill: rgba(242, 242, 242, 0.2) !important; +} + +.button--outline-success { + color: #52c41a; + border: 1px solid #52c41a; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--outline-success svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--outline-success, .button--outline-success:link, .button--outline-success:visited { + color: #52c41a; + background-color: transparent; +} +.button--outline-success svg path, .button--outline-success:link svg path, .button--outline-success:visited svg path { + fill: #52c41a; +} +.root[data-is-touch=false] .button--outline-success:hover { + color: #fff; + background-color: #52c41a; +} +.root[data-is-touch=false] .button--outline-success:hover svg path { + fill: #fff; +} +.root[data-is-touch] .button--outline-success.active, .root[data-is-touch] .button--outline-success:active { + color: #fff; + background-color: #368011; +} +.root[data-is-touch] .button--outline-success.active svg path, .root[data-is-touch] .button--outline-success:active svg path { + fill: #fff; +} +.root[data-is-touch] .button--outline-success.focus { + color: default; + background-color: #52c41a; + box-shadow: 0 0 0 2px rgba(82, 196, 26, 0.4); +} +.root[data-is-touch] .button--outline-success.focus svg path { + fill: default; +} +.button--outline-success.disabled, .button--outline-success:disabled { + color: rgba(82, 196, 26, 0.2) !important; + background-color: transparent !important; +} +.button--outline-success.disabled svg path, .button--outline-success:disabled svg path { + fill: rgba(82, 196, 26, 0.2) !important; +} + +.button--outline-info { + color: #1890ff; + border: 1px solid #1890ff; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--outline-info svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--outline-info, .button--outline-info:link, .button--outline-info:visited { + color: #1890ff; + background-color: transparent; +} +.button--outline-info svg path, .button--outline-info:link svg path, .button--outline-info:visited svg path { + fill: #1890ff; +} +.root[data-is-touch=false] .button--outline-info:hover { + color: #fff; + background-color: #1890ff; +} +.root[data-is-touch=false] .button--outline-info:hover svg path { + fill: #fff; +} +.root[data-is-touch] .button--outline-info.active, .root[data-is-touch] .button--outline-info:active { + color: #fff; + background-color: #0069cb; +} +.root[data-is-touch] .button--outline-info.active svg path, .root[data-is-touch] .button--outline-info:active svg path { + fill: #fff; +} +.root[data-is-touch] .button--outline-info.focus { + color: default; + background-color: #1890ff; + box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.4); +} +.root[data-is-touch] .button--outline-info.focus svg path { + fill: default; +} +.button--outline-info.disabled, .button--outline-info:disabled { + color: rgba(24, 144, 255, 0.2) !important; + background-color: transparent !important; +} +.button--outline-info.disabled svg path, .button--outline-info:disabled svg path { + fill: rgba(24, 144, 255, 0.2) !important; +} + +.button--outline-warning { + color: #fa8c16; + border: 1px solid #fa8c16; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--outline-warning svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--outline-warning, .button--outline-warning:link, .button--outline-warning:visited { + color: #fa8c16; + background-color: transparent; +} +.button--outline-warning svg path, .button--outline-warning:link svg path, .button--outline-warning:visited svg path { + fill: #fa8c16; +} +.root[data-is-touch=false] .button--outline-warning:hover { + color: #fff; + background-color: #fa8c16; +} +.root[data-is-touch=false] .button--outline-warning:hover svg path { + fill: #fff; +} +.root[data-is-touch] .button--outline-warning.active, .root[data-is-touch] .button--outline-warning:active { + color: #fff; + background-color: #bf6504; +} +.root[data-is-touch] .button--outline-warning.active svg path, .root[data-is-touch] .button--outline-warning:active svg path { + fill: #fff; +} +.root[data-is-touch] .button--outline-warning.focus { + color: default; + background-color: #fa8c16; + box-shadow: 0 0 0 2px rgba(250, 140, 22, 0.4); +} +.root[data-is-touch] .button--outline-warning.focus svg path { + fill: default; +} +.button--outline-warning.disabled, .button--outline-warning:disabled { + color: rgba(250, 140, 22, 0.2) !important; + background-color: transparent !important; +} +.button--outline-warning.disabled svg path, .button--outline-warning:disabled svg path { + fill: rgba(250, 140, 22, 0.2) !important; +} + +.button--outline-error { + color: #f5222d; + border: 1px solid #f5222d; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--outline-error svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--outline-error, .button--outline-error:link, .button--outline-error:visited { + color: #f5222d; + background-color: transparent; +} +.button--outline-error svg path, .button--outline-error:link svg path, .button--outline-error:visited svg path { + fill: #f5222d; +} +.root[data-is-touch=false] .button--outline-error:hover { + color: #fff; + background-color: #f5222d; +} +.root[data-is-touch=false] .button--outline-error:hover svg path { + fill: #fff; +} +.root[data-is-touch] .button--outline-error.active, .root[data-is-touch] .button--outline-error:active { + color: #fff; + background-color: #c20912; +} +.root[data-is-touch] .button--outline-error.active svg path, .root[data-is-touch] .button--outline-error:active svg path { + fill: #fff; +} +.root[data-is-touch] .button--outline-error.focus { + color: default; + background-color: #f5222d; + box-shadow: 0 0 0 2px rgba(245, 34, 45, 0.4); +} +.root[data-is-touch] .button--outline-error.focus svg path { + fill: default; +} +.button--outline-error.disabled, .button--outline-error:disabled { + color: rgba(245, 34, 45, 0.2) !important; + background-color: transparent !important; +} +.button--outline-error.disabled svg path, .button--outline-error:disabled svg path { + fill: rgba(245, 34, 45, 0.2) !important; +} + +.button--outline-theme-light { + color: rgba(0, 0, 0, 0.9); + border: 1px solid rgba(0, 0, 0, 0.9); + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--outline-theme-light svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--outline-theme-light, .button--outline-theme-light:link, .button--outline-theme-light:visited { + color: rgba(0, 0, 0, 0.9); + background-color: transparent; +} +.button--outline-theme-light svg path, .button--outline-theme-light:link svg path, .button--outline-theme-light:visited svg path { + fill: rgba(0, 0, 0, 0.9); +} +.root[data-is-touch=false] .button--outline-theme-light:hover { + color: rgba(255, 255, 255, 0.95); + background-color: rgba(0, 0, 0, 0.9); +} +.root[data-is-touch=false] .button--outline-theme-light:hover svg path { + fill: rgba(255, 255, 255, 0.95); +} +.root[data-is-touch] .button--outline-theme-light.active, .root[data-is-touch] .button--outline-theme-light:active { + color: rgba(255, 255, 255, 0.95); + background-color: rgba(41, 41, 41, 0.9); +} +.root[data-is-touch] .button--outline-theme-light.active svg path, .root[data-is-touch] .button--outline-theme-light:active svg path { + fill: rgba(255, 255, 255, 0.95); +} +.root[data-is-touch] .button--outline-theme-light.focus { + color: default; + background-color: rgba(0, 0, 0, 0.9); + box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.4); +} +.root[data-is-touch] .button--outline-theme-light.focus svg path { + fill: default; +} +.button--outline-theme-light.disabled, .button--outline-theme-light:disabled { + color: rgba(0, 0, 0, 0.4) !important; + background-color: transparent !important; +} +.button--outline-theme-light.disabled svg path, .button--outline-theme-light:disabled svg path { + fill: rgba(0, 0, 0, 0.4) !important; +} + +.button--outline-theme-dark { + color: rgba(255, 255, 255, 0.9); + border: 1px solid rgba(255, 255, 255, 0.9); + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--outline-theme-dark svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.button--outline-theme-dark, .button--outline-theme-dark:link, .button--outline-theme-dark:visited { + color: rgba(255, 255, 255, 0.9); + background-color: transparent; +} +.button--outline-theme-dark svg path, .button--outline-theme-dark:link svg path, .button--outline-theme-dark:visited svg path { + fill: rgba(255, 255, 255, 0.9); +} +.root[data-is-touch=false] .button--outline-theme-dark:hover { + color: #222; + background-color: rgba(255, 255, 255, 0.9); +} +.root[data-is-touch=false] .button--outline-theme-dark:hover svg path { + fill: #222; +} +.root[data-is-touch] .button--outline-theme-dark.active, .root[data-is-touch] .button--outline-theme-dark:active { + color: #222; + background-color: rgba(217, 217, 217, 0.9); +} +.root[data-is-touch] .button--outline-theme-dark.active svg path, .root[data-is-touch] .button--outline-theme-dark:active svg path { + fill: #222; +} +.root[data-is-touch] .button--outline-theme-dark.focus { + color: default; + background-color: rgba(255, 255, 255, 0.9); + box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.4); +} +.root[data-is-touch] .button--outline-theme-dark.focus svg path { + fill: default; +} +.button--outline-theme-dark.disabled, .button--outline-theme-dark:disabled { + color: rgba(255, 255, 255, 0.2) !important; + background-color: transparent !important; +} +.button--outline-theme-dark.disabled svg path, .button--outline-theme-dark:disabled svg path { + fill: rgba(255, 255, 255, 0.2) !important; +} + +.button--pill { + border-radius: 6rem; +} + +.button--rounded { + border-radius: 0.4rem; +} + +.button--circle, .swiper__button { + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -moz-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; + -webkit-box-pack: center; + -ms-flex-pack: center; + -webkit-justify-content: center; + -moz-justify-content: center; + justify-content: center; + -webkit-box-align: center; + -ms-flex-align: center; + -webkit-align-items: center; + -moz-align-items: center; + align-items: center; + border-radius: 50%; +} + +.button--md, .button--pill, .button--rounded, .button--circle, .swiper__button { + padding: 0.45rem 0.6333333333rem; + font-size: 1rem; +} +.button--md.button--circle, .button--circle, .swiper__button { + width: 1.9rem; + height: 1.9rem; +} + +.button--xs { + padding: 0.25rem 0.4rem; + font-size: 0.7rem; +} +.button--xs.button--circle, .button--xs.swiper__button { + width: 1.2rem; + height: 1.2rem; +} + +.button--sm { + padding: 0.325rem 0.5rem; + font-size: 0.85rem; +} +.button--sm.button--circle, .button--sm.swiper__button { + width: 1.5rem; + height: 1.5rem; +} + +.button--lg { + padding: 0.525rem 0.7666666667rem; + font-size: 1.25rem; +} +.button--lg.button--circle, .button--lg.swiper__button { + width: 2.3rem; + height: 2.3rem; +} + +.button--xl { + padding: 0.65rem 0.9333333333rem; + font-size: 1.5rem; +} +.button--xl.button--circle, .button--xl.swiper__button { + width: 2.8rem; + height: 2.8rem; +} + +.image { + max-width: 100%; +} + +.image--md, .image { + width: 12rem; +} + +.image--xl { + width: 20em; +} + +.image--lg { + width: 16rem; +} + +.image--sm { + width: 8rem; +} + +.image--xs { + width: 4rem; +} + +.card { + max-width: 18rem; + border-radius: 0.4rem; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.23), 0 1px 3px rgba(0, 0, 0, 0.08), 0 6px 12px rgba(0, 0, 0, 0.02); + -webkit-transition: box-shadow 0.4s ease-in-out; + transition: box-shadow 0.4s ease-in-out; +} +.card > :first-child { + border-top-left-radius: 0.4rem; + border-top-right-radius: 0.4rem; +} +.card > :last-child { + border-bottom-right-radius: 0.4rem; + border-bottom-left-radius: 0.4rem; +} + +.cell > .card { + max-width: unset; +} + +.card__content { + padding: 0.5rem 1rem; +} + +.card__header, .card__header > a { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.card__header, .card__header:link, .card__header:visited, .card__header > a, .card__header > a:link, .card__header > a:visited { + text-decoration: none; +} +.root[data-is-touch=false] .card__header:hover, .root[data-is-touch=false] .card__header > a:hover { + text-decoration: underline; +} +.root[data-is-touch] .card__header.active, .root[data-is-touch] .card__header:active, .root[data-is-touch] .card__header > a.active, .root[data-is-touch] .card__header > a:active { + text-decoration: none; +} +.card__header, .card__header:link, .card__header:visited, .card__header > a, .card__header > a:link, .card__header > a:visited { + color: #000; +} +.root[data-is-touch=false] .card__header:hover, .root[data-is-touch=false] .card__header > a:hover { + color: #fc4d50; +} +.root[data-is-touch] .card__header.active, .root[data-is-touch] .card__header:active, .root[data-is-touch] .card__header > a.active, .root[data-is-touch] .card__header > a:active { + color: #f80408; +} +.card__header.disabled, .card__header:disabled, .card__header > a.disabled, .card__header > a:disabled { + color: rgba(0, 0, 0, 0.2) !important; +} + +.card__image { + position: relative; + width: 100%; +} +.card__image > img { + display: block; + width: 100%; + height: auto; + border-radius: inherit; +} +.card__image > .overlay { + position: absolute; + width: 100%; + max-height: 100%; + padding: 0.5rem; +} +.card__image > .overlay a { + text-decoration: none !important; +} +.card__image > .overlay, .card__image > .overlay--top { + top: 0; + bottom: auto; + border-top-left-radius: inherit; + border-top-right-radius: inherit; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.card__image > .overlay--bottom { + top: auto; + bottom: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-right-radius: inherit; + border-bottom-left-radius: inherit; +} +.card__image > .overlay--full { + top: 0; + bottom: 0; +} +.card__image > .overlay, .card__image > .overlay--dark { + background-color: rgba(0, 0, 0, 0.4); +} +.card__image > .overlay--light { + background: rgba(255, 255, 255, 0.4); +} + +.card--clickable { + cursor: pointer; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.root[data-is-touch=false] .card--clickable:hover { + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.23), 0 2px 6px rgba(0, 0, 0, 0.08), 0 12px 24px rgba(0, 0, 0, 0.02); +} +.root[data-is-touch=false] .card--clickable:hover .card__image > img { + height: inherit; +} + +.card--flat { + box-shadow: none; +} +.card--flat .card__image > img { + border-radius: 0.4rem; +} +.card--flat .card__content { + padding-top: 0; + padding-left: 0; +} + +.gallery { + height: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -moz-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-direction: normal; + -webkit-box-orient: vertical; + -webkit-flex-direction: column; + -moz-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; +} + +.gallery__swiper { + -webkit-box-flex: 1; + -webkit-flex: 1; + -moz-box-flex: 1; + -moz-flex: 1; + -ms-flex: 1; + flex: 1; +} + +.gallery-item { + display: -webkit-box; + display: -webkit-flex; + display: -moz-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + -webkit-align-items: center; + -moz-align-items: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + -webkit-justify-content: center; + -moz-justify-content: center; + justify-content: center; + height: 100%; + overflow: hidden; +} + +.gallery-item__main { + display: block; +} + +.hero { + background-position: 50% 50%; + display: -webkit-box; + display: -webkit-flex; + display: -moz-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-direction: normal; + -webkit-box-orient: vertical; + -webkit-flex-direction: column; + -moz-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: center; + -ms-flex-pack: center; + -webkit-justify-content: center; + -moz-justify-content: center; + justify-content: center; + background-size: cover; +} +.hero h1 { + font-size: 3.5rem; +} +.hero h2 { + font-size: 2.5rem; +} +.hero h3 { + font-size: 2rem; +} +.hero h4 { + font-size: 1.75rem; +} +.hero h5 { + font-size: 1.5rem; +} +.hero h6 { + font-size: 1.5rem; +} +.hero p { + font-size: 1.5rem; +} +@media (max-width: 1023px) { + .hero h1 { + font-size: 3rem; + } + .hero h2 { + font-size: 2rem; + } + .hero h3 { + font-size: 1.75rem; + } + .hero h4 { + font-size: 1.5rem; + } + .hero h5 { + font-size: 1.25rem; + } + .hero h6 { + font-size: 1.25rem; + } + .hero p { + font-size: 1.25rem; + } +} +@media (max-width: 499px) { + .hero h1 { + font-size: 2rem; + } + .hero h2 { + font-size: 1.5rem; + } + .hero h3 { + font-size: 1.35rem; + } + .hero h4 { + font-size: 1.15rem; + } + .hero h5 { + font-size: 1rem; + } + .hero h6 { + font-size: 1rem; + } + .hero p { + font-size: 1rem; + } +} + +.hero--center { + text-align: center; +} +.hero__content { + margin: 3rem; +} +@media (max-width: 1023px) { + .hero__content { + margin: 3rem 1.5rem; + } +} +@media (max-width: 499px) { + .hero__content { + margin: 1.5rem 1rem; + } +} + +.heros > .hero { + margin: 3rem; +} +@media (max-width: 1023px) { + .heros > .hero { + margin: 1rem; + } +} +@media (max-width: 499px) { + .heros > .hero { + margin: 0.5rem 0; + } +} + +.menu { + display: -webkit-box; + display: -webkit-flex; + display: -moz-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-wrap: wrap; + -moz-flex-wrap: wrap; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin-top: 0; + margin-bottom: 0; + -webkit-box-direction: normal; + -webkit-box-orient: horizontal; + -webkit-flex-direction: row; + -moz-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-align: center; + -ms-flex-align: center; + -webkit-align-items: center; + -moz-align-items: center; + align-items: center; +} +.menu > li { + margin-top: 0.25rem; + margin-bottom: 0.25rem; + margin-right: 0.25rem; + list-style-type: none; +} +.menu > li:last-child { + margin-right: 0; +} + +.menu--vertical { + -webkit-box-direction: normal; + -webkit-box-orient: vertical; + -webkit-flex-direction: column; + -moz-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-align: normal; + -ms-flex-align: normal; + -webkit-align-items: normal; + -moz-align-items: normal; + align-items: normal; +} +.menu--vertical > li { + margin-right: 0; +} + +.menu--inline { + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -moz-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; +} + +.menu--center, .hero--center .menu { + -webkit-box-pack: center; + -ms-flex-pack: center; + -webkit-justify-content: center; + -moz-justify-content: center; + justify-content: center; +} + +.menu--nowrap { + -webkit-flex-wrap: nowrap; + -moz-flex-wrap: nowrap; + -ms-flex-wrap: none; + flex-wrap: nowrap; +} + +.menu--grow { + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + -moz-flex-grow: 1; + -ms-flex-positive: 1; + flex-grow: 1; +} + +.modal { + position: fixed; + top: 0; + left: 0; + z-index: 999; + width: 100%; + height: 100%; + color: rgba(255, 255, 255, 0.95); + touch-action: none; + background-color: rgba(0, 0, 0, 0.9); + opacity: 0; + -webkit-transform: translate(100%, 0); + transform: translate(100%, 0); + -webkit-transition: opacity 0.4s ease-in-out, transform 0s 0.4s ease-in-out; + transition: opacity 0.4s ease-in-out, transform 0s 0.4s ease-in-out; +} + +.modal--show { + opacity: 1; + -webkit-transform: translate(0, 0); + transform: translate(0, 0); + -webkit-transition: opacity 0.4s ease-in-out; + transition: opacity 0.4s ease-in-out; +} + +.modal--overflow { + overflow: auto; + -webkit-overflow-scrolling: touch; +} + +ul.toc { + display: block; + margin: 0; + color: #222; + list-style-type: none; +} +ul.toc > li { + margin: 0.125rem 0; +} +ul.toc > li a { + display: inline-block; + margin: 0.0625rem 0; + text-decoration: none !important; +} +ul.toc .toc-h1 a, +ul.toc .toc-h2 a, +ul.toc .toc-h3 a, +ul.toc .toc-h4 a, +ul.toc .toc-h5 a, +ul.toc .toc-h6 a { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +ul.toc .toc-h1 a, ul.toc .toc-h1 a:link, ul.toc .toc-h1 a:visited, +ul.toc .toc-h2 a, +ul.toc .toc-h2 a:link, +ul.toc .toc-h2 a:visited, +ul.toc .toc-h3 a, +ul.toc .toc-h3 a:link, +ul.toc .toc-h3 a:visited, +ul.toc .toc-h4 a, +ul.toc .toc-h4 a:link, +ul.toc .toc-h4 a:visited, +ul.toc .toc-h5 a, +ul.toc .toc-h5 a:link, +ul.toc .toc-h5 a:visited, +ul.toc .toc-h6 a, +ul.toc .toc-h6 a:link, +ul.toc .toc-h6 a:visited { + text-decoration: none; +} +.root[data-is-touch=false] ul.toc .toc-h1 a:hover, +.root[data-is-touch=false] ul.toc .toc-h2 a:hover, +.root[data-is-touch=false] ul.toc .toc-h3 a:hover, +.root[data-is-touch=false] ul.toc .toc-h4 a:hover, +.root[data-is-touch=false] ul.toc .toc-h5 a:hover, +.root[data-is-touch=false] ul.toc .toc-h6 a:hover { + text-decoration: underline; +} +.root[data-is-touch] ul.toc .toc-h1 a.active, .root[data-is-touch] ul.toc .toc-h1 a:active, +.root[data-is-touch] ul.toc .toc-h2 a.active, +.root[data-is-touch] ul.toc .toc-h2 a:active, +.root[data-is-touch] ul.toc .toc-h3 a.active, +.root[data-is-touch] ul.toc .toc-h3 a:active, +.root[data-is-touch] ul.toc .toc-h4 a.active, +.root[data-is-touch] ul.toc .toc-h4 a:active, +.root[data-is-touch] ul.toc .toc-h5 a.active, +.root[data-is-touch] ul.toc .toc-h5 a:active, +.root[data-is-touch] ul.toc .toc-h6 a.active, +.root[data-is-touch] ul.toc .toc-h6 a:active { + text-decoration: none; +} +ul.toc .toc-h1 a, ul.toc .toc-h1 a:link, ul.toc .toc-h1 a:visited, +ul.toc .toc-h2 a, +ul.toc .toc-h2 a:link, +ul.toc .toc-h2 a:visited, +ul.toc .toc-h3 a, +ul.toc .toc-h3 a:link, +ul.toc .toc-h3 a:visited, +ul.toc .toc-h4 a, +ul.toc .toc-h4 a:link, +ul.toc .toc-h4 a:visited, +ul.toc .toc-h5 a, +ul.toc .toc-h5 a:link, +ul.toc .toc-h5 a:visited, +ul.toc .toc-h6 a, +ul.toc .toc-h6 a:link, +ul.toc .toc-h6 a:visited { + color: #222; +} +.root[data-is-touch=false] ul.toc .toc-h1 a:hover, +.root[data-is-touch=false] ul.toc .toc-h2 a:hover, +.root[data-is-touch=false] ul.toc .toc-h3 a:hover, +.root[data-is-touch=false] ul.toc .toc-h4 a:hover, +.root[data-is-touch=false] ul.toc .toc-h5 a:hover, +.root[data-is-touch=false] ul.toc .toc-h6 a:hover { + color: #fc4d50; +} +.root[data-is-touch] ul.toc .toc-h1 a.active, .root[data-is-touch] ul.toc .toc-h1 a:active, +.root[data-is-touch] ul.toc .toc-h2 a.active, +.root[data-is-touch] ul.toc .toc-h2 a:active, +.root[data-is-touch] ul.toc .toc-h3 a.active, +.root[data-is-touch] ul.toc .toc-h3 a:active, +.root[data-is-touch] ul.toc .toc-h4 a.active, +.root[data-is-touch] ul.toc .toc-h4 a:active, +.root[data-is-touch] ul.toc .toc-h5 a.active, +.root[data-is-touch] ul.toc .toc-h5 a:active, +.root[data-is-touch] ul.toc .toc-h6 a.active, +.root[data-is-touch] ul.toc .toc-h6 a:active { + color: #f80408; +} +ul.toc .toc-h1 a.disabled, ul.toc .toc-h1 a:disabled, +ul.toc .toc-h2 a.disabled, +ul.toc .toc-h2 a:disabled, +ul.toc .toc-h3 a.disabled, +ul.toc .toc-h3 a:disabled, +ul.toc .toc-h4 a.disabled, +ul.toc .toc-h4 a:disabled, +ul.toc .toc-h5 a.disabled, +ul.toc .toc-h5 a:disabled, +ul.toc .toc-h6 a.disabled, +ul.toc .toc-h6 a:disabled { + color: rgba(34, 34, 34, 0.2) !important; +} +ul.toc .toc-h1.active a, +ul.toc .toc-h2.active a, +ul.toc .toc-h3.active a, +ul.toc .toc-h4.active a, +ul.toc .toc-h5.active a, +ul.toc .toc-h6.active a { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +ul.toc .toc-h1.active a, ul.toc .toc-h1.active a:link, ul.toc .toc-h1.active a:visited, +ul.toc .toc-h2.active a, +ul.toc .toc-h2.active a:link, +ul.toc .toc-h2.active a:visited, +ul.toc .toc-h3.active a, +ul.toc .toc-h3.active a:link, +ul.toc .toc-h3.active a:visited, +ul.toc .toc-h4.active a, +ul.toc .toc-h4.active a:link, +ul.toc .toc-h4.active a:visited, +ul.toc .toc-h5.active a, +ul.toc .toc-h5.active a:link, +ul.toc .toc-h5.active a:visited, +ul.toc .toc-h6.active a, +ul.toc .toc-h6.active a:link, +ul.toc .toc-h6.active a:visited { + text-decoration: none; +} +.root[data-is-touch=false] ul.toc .toc-h1.active a:hover, +.root[data-is-touch=false] ul.toc .toc-h2.active a:hover, +.root[data-is-touch=false] ul.toc .toc-h3.active a:hover, +.root[data-is-touch=false] ul.toc .toc-h4.active a:hover, +.root[data-is-touch=false] ul.toc .toc-h5.active a:hover, +.root[data-is-touch=false] ul.toc .toc-h6.active a:hover { + text-decoration: underline; +} +.root[data-is-touch] ul.toc .toc-h1.active a.active, .root[data-is-touch] ul.toc .toc-h1.active a:active, +.root[data-is-touch] ul.toc .toc-h2.active a.active, +.root[data-is-touch] ul.toc .toc-h2.active a:active, +.root[data-is-touch] ul.toc .toc-h3.active a.active, +.root[data-is-touch] ul.toc .toc-h3.active a:active, +.root[data-is-touch] ul.toc .toc-h4.active a.active, +.root[data-is-touch] ul.toc .toc-h4.active a:active, +.root[data-is-touch] ul.toc .toc-h5.active a.active, +.root[data-is-touch] ul.toc .toc-h5.active a:active, +.root[data-is-touch] ul.toc .toc-h6.active a.active, +.root[data-is-touch] ul.toc .toc-h6.active a:active { + text-decoration: none; +} +ul.toc .toc-h1.active a, ul.toc .toc-h1.active a:link, ul.toc .toc-h1.active a:visited, +ul.toc .toc-h2.active a, +ul.toc .toc-h2.active a:link, +ul.toc .toc-h2.active a:visited, +ul.toc .toc-h3.active a, +ul.toc .toc-h3.active a:link, +ul.toc .toc-h3.active a:visited, +ul.toc .toc-h4.active a, +ul.toc .toc-h4.active a:link, +ul.toc .toc-h4.active a:visited, +ul.toc .toc-h5.active a, +ul.toc .toc-h5.active a:link, +ul.toc .toc-h5.active a:visited, +ul.toc .toc-h6.active a, +ul.toc .toc-h6.active a:link, +ul.toc .toc-h6.active a:visited { + color: #fc4d50; +} +.root[data-is-touch=false] ul.toc .toc-h1.active a:hover, +.root[data-is-touch=false] ul.toc .toc-h2.active a:hover, +.root[data-is-touch=false] ul.toc .toc-h3.active a:hover, +.root[data-is-touch=false] ul.toc .toc-h4.active a:hover, +.root[data-is-touch=false] ul.toc .toc-h5.active a:hover, +.root[data-is-touch=false] ul.toc .toc-h6.active a:hover { + color: #fb070b; +} +.root[data-is-touch] ul.toc .toc-h1.active a.active, .root[data-is-touch] ul.toc .toc-h1.active a:active, +.root[data-is-touch] ul.toc .toc-h2.active a.active, +.root[data-is-touch] ul.toc .toc-h2.active a:active, +.root[data-is-touch] ul.toc .toc-h3.active a.active, +.root[data-is-touch] ul.toc .toc-h3.active a:active, +.root[data-is-touch] ul.toc .toc-h4.active a.active, +.root[data-is-touch] ul.toc .toc-h4.active a:active, +.root[data-is-touch] ul.toc .toc-h5.active a.active, +.root[data-is-touch] ul.toc .toc-h5.active a:active, +.root[data-is-touch] ul.toc .toc-h6.active a.active, +.root[data-is-touch] ul.toc .toc-h6.active a:active { + color: #b20306; +} +ul.toc .toc-h1.active a.disabled, ul.toc .toc-h1.active a:disabled, +ul.toc .toc-h2.active a.disabled, +ul.toc .toc-h2.active a:disabled, +ul.toc .toc-h3.active a.disabled, +ul.toc .toc-h3.active a:disabled, +ul.toc .toc-h4.active a.disabled, +ul.toc .toc-h4.active a:disabled, +ul.toc .toc-h5.active a.disabled, +ul.toc .toc-h5.active a:disabled, +ul.toc .toc-h6.active a.disabled, +ul.toc .toc-h6.active a:disabled { + color: rgba(252, 77, 80, 0.2) !important; +} +ul.toc .toc-h2, ul.toc .toc-h2 a, +ul.toc .toc-h3, +ul.toc .toc-h3 a, +ul.toc .toc-h4, +ul.toc .toc-h4 a, +ul.toc .toc-h5, +ul.toc .toc-h5 a, +ul.toc .toc-h6, +ul.toc .toc-h6 a { + font-size: 0.7rem; + font-weight: 400; + line-height: 1.2; +} +ul.toc .toc-h1 { + border: 0 solid #e6e6e6; + border-bottom-width: 1px; + padding: 0.5rem 0 0.25rem 0; + margin-bottom: 0.5rem; + color: #000; +} +ul.toc .toc-h1, ul.toc .toc-h1 a { + font-size: 0.85rem; + font-weight: 700; + line-height: 1.4; +} +ul.toc .toc-h1 a { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +ul.toc .toc-h1 a, ul.toc .toc-h1 a:link, ul.toc .toc-h1 a:visited { + text-decoration: none; +} +.root[data-is-touch=false] ul.toc .toc-h1 a:hover { + text-decoration: underline; +} +.root[data-is-touch] ul.toc .toc-h1 a.active, .root[data-is-touch] ul.toc .toc-h1 a:active { + text-decoration: none; +} +ul.toc .toc-h1 a, ul.toc .toc-h1 a:link, ul.toc .toc-h1 a:visited { + color: #000; +} +.root[data-is-touch=false] ul.toc .toc-h1 a:hover { + color: #fc4d50; +} +.root[data-is-touch] ul.toc .toc-h1 a.active, .root[data-is-touch] ul.toc .toc-h1 a:active { + color: #f80408; +} +ul.toc .toc-h1 a.disabled, ul.toc .toc-h1 a:disabled { + color: rgba(0, 0, 0, 0.2) !important; +} +ul.toc .toc-h2, ul.toc .toc-h2 a { + font-weight: 700; +} +ul.toc .toc-h3 { + margin-left: 1rem; +} +ul.toc .toc-h4 { + margin-left: 2rem; +} +ul.toc .toc-h5, +ul.toc .toc-h6 { + margin-left: 3rem; +} +ul.toc .toc-h6 { + color: #888; +} +ul.toc .toc-h6 a { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +ul.toc .toc-h6 a, ul.toc .toc-h6 a:link, ul.toc .toc-h6 a:visited { + text-decoration: none; +} +.root[data-is-touch=false] ul.toc .toc-h6 a:hover { + text-decoration: underline; +} +.root[data-is-touch] ul.toc .toc-h6 a.active, .root[data-is-touch] ul.toc .toc-h6 a:active { + text-decoration: none; +} +ul.toc .toc-h6 a, ul.toc .toc-h6 a:link, ul.toc .toc-h6 a:visited { + color: #888; +} +.root[data-is-touch=false] ul.toc .toc-h6 a:hover { + color: #fc4d50; +} +.root[data-is-touch] ul.toc .toc-h6 a.active, .root[data-is-touch] ul.toc .toc-h6 a:active { + color: #f80408; +} +ul.toc .toc-h6 a.disabled, ul.toc .toc-h6 a:disabled { + color: rgba(136, 136, 136, 0.2) !important; +} + +ul.toc--ellipsis > li { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +ul.toc--navigator > li a { + padding-left: 0.5rem; + margin: 0.25rem 0; +} +ul.toc--navigator > li.active a { + margin-left: -4px; + border: 0 solid #fc4d50; + border-left-width: 4px; +} +ul.toc--navigator .toc-h2, +ul.toc--navigator .toc-h3, +ul.toc--navigator .toc-h4 { + color: #888; +} +ul.toc--navigator .toc-h2 a, +ul.toc--navigator .toc-h3 a, +ul.toc--navigator .toc-h4 a { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +ul.toc--navigator .toc-h2 a, ul.toc--navigator .toc-h2 a:link, ul.toc--navigator .toc-h2 a:visited, +ul.toc--navigator .toc-h3 a, +ul.toc--navigator .toc-h3 a:link, +ul.toc--navigator .toc-h3 a:visited, +ul.toc--navigator .toc-h4 a, +ul.toc--navigator .toc-h4 a:link, +ul.toc--navigator .toc-h4 a:visited { + text-decoration: none; +} +.root[data-is-touch=false] ul.toc--navigator .toc-h2 a:hover, +.root[data-is-touch=false] ul.toc--navigator .toc-h3 a:hover, +.root[data-is-touch=false] ul.toc--navigator .toc-h4 a:hover { + text-decoration: underline; +} +.root[data-is-touch] ul.toc--navigator .toc-h2 a.active, .root[data-is-touch] ul.toc--navigator .toc-h2 a:active, +.root[data-is-touch] ul.toc--navigator .toc-h3 a.active, +.root[data-is-touch] ul.toc--navigator .toc-h3 a:active, +.root[data-is-touch] ul.toc--navigator .toc-h4 a.active, +.root[data-is-touch] ul.toc--navigator .toc-h4 a:active { + text-decoration: none; +} +ul.toc--navigator .toc-h2 a, ul.toc--navigator .toc-h2 a:link, ul.toc--navigator .toc-h2 a:visited, +ul.toc--navigator .toc-h3 a, +ul.toc--navigator .toc-h3 a:link, +ul.toc--navigator .toc-h3 a:visited, +ul.toc--navigator .toc-h4 a, +ul.toc--navigator .toc-h4 a:link, +ul.toc--navigator .toc-h4 a:visited { + color: #888; +} +.root[data-is-touch=false] ul.toc--navigator .toc-h2 a:hover, +.root[data-is-touch=false] ul.toc--navigator .toc-h3 a:hover, +.root[data-is-touch=false] ul.toc--navigator .toc-h4 a:hover { + color: #646464; +} +.root[data-is-touch] ul.toc--navigator .toc-h2 a.active, .root[data-is-touch] ul.toc--navigator .toc-h2 a:active, +.root[data-is-touch] ul.toc--navigator .toc-h3 a.active, +.root[data-is-touch] ul.toc--navigator .toc-h3 a:active, +.root[data-is-touch] ul.toc--navigator .toc-h4 a.active, +.root[data-is-touch] ul.toc--navigator .toc-h4 a:active { + color: #3e3e3e; +} +ul.toc--navigator .toc-h2 a.disabled, ul.toc--navigator .toc-h2 a:disabled, +ul.toc--navigator .toc-h3 a.disabled, +ul.toc--navigator .toc-h3 a:disabled, +ul.toc--navigator .toc-h4 a.disabled, +ul.toc--navigator .toc-h4 a:disabled { + color: rgba(136, 136, 136, 0.2) !important; +} +ul.toc--navigator .toc-h1 { + color: #222; +} +ul.toc--navigator .toc-h1, ul.toc--navigator .toc-h1 a { + font-size: 1rem; + line-height: 1.6; +} +ul.toc--navigator .toc-h1 a { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +ul.toc--navigator .toc-h1 a, ul.toc--navigator .toc-h1 a:link, ul.toc--navigator .toc-h1 a:visited { + text-decoration: none; +} +.root[data-is-touch=false] ul.toc--navigator .toc-h1 a:hover { + text-decoration: underline; +} +.root[data-is-touch] ul.toc--navigator .toc-h1 a.active, .root[data-is-touch] ul.toc--navigator .toc-h1 a:active { + text-decoration: none; +} +ul.toc--navigator .toc-h1 a, ul.toc--navigator .toc-h1 a:link, ul.toc--navigator .toc-h1 a:visited { + color: #000; +} +.root[data-is-touch=false] ul.toc--navigator .toc-h1 a:hover { + color: #2e2e2e; +} +.root[data-is-touch] ul.toc--navigator .toc-h1 a.active, .root[data-is-touch] ul.toc--navigator .toc-h1 a:active { + color: #575757; +} +ul.toc--navigator .toc-h1 a.disabled, ul.toc--navigator .toc-h1 a:disabled { + color: rgba(0, 0, 0, 0.4) !important; +} +ul.toc--navigator .toc-h2, ul.toc--navigator .toc-h2 a { + font-size: 0.85rem; + font-weight: 700; + line-height: 1.4; +} + +.item { + display: -webkit-box; + display: -webkit-flex; + display: -moz-flex; + display: -ms-flexbox; + display: flex; +} +@media (max-width: 499px) { + .item { + -webkit-box-direction: normal; + -webkit-box-orient: vertical; + -webkit-flex-direction: column; + -moz-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + } +} + +.item__image { + margin-right: 1rem; +} +.item__image + .item__content > :first-child { + margin-top: 0; +} +.item__image + .item__content > :first-child > :first-child { + margin-top: 0; +} +@media (max-width: 499px) { + .item__image { + margin-right: 0; + } +} + +.item__content { + -webkit-box-flex: 1; + -webkit-flex: 1; + -moz-box-flex: 1; + -moz-flex: 1; + -ms-flex: 1; + flex: 1; + min-width: 0; +} + +a > .item__header, a.item__header, .item__header > a { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +a > .item__header, a > .item__header:link, a > .item__header:visited, a.item__header, a.item__header:link, a.item__header:visited, .item__header > a, .item__header > a:link, .item__header > a:visited { + text-decoration: none; +} +.root[data-is-touch=false] a > .item__header:hover, .root[data-is-touch=false] a.item__header:hover, .root[data-is-touch=false] .item__header > a:hover { + text-decoration: underline; +} +.root[data-is-touch] a > .item__header.active, .root[data-is-touch] a > .item__header:active, .root[data-is-touch] a.item__header.active, .root[data-is-touch] a.item__header:active, .root[data-is-touch] .item__header > a.active, .root[data-is-touch] .item__header > a:active { + text-decoration: none; +} +a > .item__header, a > .item__header:link, a > .item__header:visited, a.item__header, a.item__header:link, a.item__header:visited, .item__header > a, .item__header > a:link, .item__header > a:visited { + color: #000; +} +.root[data-is-touch=false] a > .item__header:hover, .root[data-is-touch=false] a.item__header:hover, .root[data-is-touch=false] .item__header > a:hover { + color: #fc4d50; +} +.root[data-is-touch] a > .item__header.active, .root[data-is-touch] a > .item__header:active, .root[data-is-touch] a.item__header.active, .root[data-is-touch] a.item__header:active, .root[data-is-touch] .item__header > a.active, .root[data-is-touch] .item__header > a:active { + color: #f80408; +} +a > .item__header.disabled, a > .item__header:disabled, a.item__header.disabled, a.item__header:disabled, .item__header > a.disabled, .item__header > a:disabled { + color: rgba(0, 0, 0, 0.2) !important; +} + +.item__meta { + color: #888; +} + +.item__description, .item__description .article__content { + font-size: 0.85rem; + line-height: 1.6; +} +.item__description h1, +.item__description h2, +.item__description h3, +.item__description h4, +.item__description h5, +.item__description h6, +.item__description p, +.item__description hr, +.item__description blockquote, +.item__description figure, +.item__description pre, +.item__description .highlighter-rouge, +.item__description ul, +.item__description ol, +.item__description dl, +.item__description table, +.item__description .footnotes, .item__description .article__content h1, +.item__description .article__content h2, +.item__description .article__content h3, +.item__description .article__content h4, +.item__description .article__content h5, +.item__description .article__content h6, +.item__description .article__content p, +.item__description .article__content hr, +.item__description .article__content blockquote, +.item__description .article__content figure, +.item__description .article__content pre, +.item__description .article__content .highlighter-rouge, +.item__description .article__content ul, +.item__description .article__content ol, +.item__description .article__content dl, +.item__description .article__content table, +.item__description .article__content .footnotes { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} +.item__description h1, .item__description h2, .item__description h3, .item__description h4, .item__description h5, .item__description h6, .item__description .article__content h1, .item__description .article__content h2, .item__description .article__content h3, .item__description .article__content h4, .item__description .article__content h5, .item__description .article__content h6 { + margin-top: 1rem; +} +.item__description h1, .item__description h2, .item__description h3, .item__description .article__content h1, .item__description .article__content h2, .item__description .article__content h3 { + color: #222; +} +.item__description h1, .item__description h2, .item__description .article__content h1, .item__description .article__content h2 { + padding: 0; + border: none; +} +.item__description h1, .item__description .article__content h1 { + font-size: 1.05rem; +} +.item__description h2, .item__description .article__content h2 { + font-size: 1rem; +} +.item__description h3, .item__description .article__content h3 { + font-size: 0.95rem; +} +.item__description h4, .item__description .article__content h4 { + font-size: 0.9rem; +} +.item__description h5, .item__description .article__content h5 { + font-size: 0.85rem; +} +.item__description h6, .item__description .article__content h6 { + font-size: 0.85rem; +} +.item__description img, .item__description .article__content img { + max-height: 32rem; +} +@media (max-width: 499px) { + .item__description img, .item__description .article__content img { + max-height: 14rem; + } +} + +.items > .item:not(:last-child) { + margin-bottom: 0.5rem; +} + +.items--divided > .item { + list-style-type: none; +} +.items--divided > .item:not(:first-child) { + padding-top: 1.5rem; +} +.items--divided > .item:not(:last-child) { + padding-bottom: 1.5rem; + border: 0 solid #e6e6e6; + border-bottom-width: 1px; +} + +.swiper { + position: relative; + overflow: hidden; +} + +.swiper__wrapper, .swiper__slide { + width: 100%; + height: 100%; +} + +.swiper__wrapper { + display: -webkit-box; + display: -webkit-flex; + display: -moz-flex; + display: -ms-flexbox; + display: flex; +} + +.swiper__wrapper--animation { + -webkit-transition: transform 0.4s ease-in-out; + transition: transform 0.4s ease-in-out; +} + +.swiper__slide { + -webkit-flex-shrink: 0; + -moz-flex-shrink: 0; + -ms-flex-negative: 0; + flex-shrink: 0; +} +.swiper__slide > img { + max-width: 100%; +} + +.swiper__button { + position: absolute; + top: 50%; + -webkit-transform: translate(0, -50%); + transform: translate(0, -50%); + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.swiper__button svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.swiper__button, .swiper__button:link, .swiper__button:visited { + color: #000; + background-color: rgba(242, 242, 242, 0.4); +} +.swiper__button svg path, .swiper__button:link svg path, .swiper__button:visited svg path { + fill: #000; +} +.root[data-is-touch=false] .swiper__button:hover { + color: #000; + background-color: rgba(206, 206, 206, 0.4); +} +.root[data-is-touch=false] .swiper__button:hover svg path { + fill: #000; +} +.root[data-is-touch] .swiper__button.active, .root[data-is-touch] .swiper__button:active { + color: #000; + background-color: rgba(168, 168, 168, 0.4); +} +.root[data-is-touch] .swiper__button.active svg path, .root[data-is-touch] .swiper__button:active svg path { + fill: #000; +} +.root[data-is-touch] .swiper__button.focus { + color: default; + background-color: rgba(206, 206, 206, 0.4); + box-shadow: 0 0 0 2px rgba(206, 206, 206, 0.4); +} +.root[data-is-touch] .swiper__button.focus svg path { + fill: default; +} +.swiper__button.disabled, .swiper__button:disabled { + color: rgba(0, 0, 0, 0.2) !important; + background-color: rgba(242, 242, 242, 0.4) !important; +} +.swiper__button.disabled svg path, .swiper__button:disabled svg path { + fill: rgba(0, 0, 0, 0.2) !important; +} + +.swiper--light .swiper__button { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.swiper--light .swiper__button svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.swiper--light .swiper__button, .swiper--light .swiper__button:link, .swiper--light .swiper__button:visited { + color: #222; + background-color: rgba(255, 255, 255, 0.4); +} +.swiper--light .swiper__button svg path, .swiper--light .swiper__button:link svg path, .swiper--light .swiper__button:visited svg path { + fill: #222; +} +.root[data-is-touch=false] .swiper--light .swiper__button:hover { + color: #222; + background-color: rgba(219, 219, 219, 0.4); +} +.root[data-is-touch=false] .swiper--light .swiper__button:hover svg path { + fill: #222; +} +.root[data-is-touch] .swiper--light .swiper__button.active, .root[data-is-touch] .swiper--light .swiper__button:active { + color: #222; + background-color: rgba(181, 181, 181, 0.4); +} +.root[data-is-touch] .swiper--light .swiper__button.active svg path, .root[data-is-touch] .swiper--light .swiper__button:active svg path { + fill: #222; +} +.root[data-is-touch] .swiper--light .swiper__button.focus { + color: default; + background-color: rgba(219, 219, 219, 0.4); + box-shadow: 0 0 0 2px rgba(219, 219, 219, 0.4); +} +.root[data-is-touch] .swiper--light .swiper__button.focus svg path { + fill: default; +} +.swiper--light .swiper__button.disabled, .swiper--light .swiper__button:disabled { + color: rgba(34, 34, 34, 0.2) !important; + background-color: rgba(255, 255, 255, 0.4) !important; +} +.swiper--light .swiper__button.disabled svg path, .swiper--light .swiper__button:disabled svg path { + fill: rgba(34, 34, 34, 0.2) !important; +} + +.swiper--dark .swiper__button { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.swiper--dark .swiper__button svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.swiper--dark .swiper__button, .swiper--dark .swiper__button:link, .swiper--dark .swiper__button:visited { + color: rgba(255, 255, 255, 0.95); + background-color: rgba(0, 0, 0, 0.4); +} +.swiper--dark .swiper__button svg path, .swiper--dark .swiper__button:link svg path, .swiper--dark .swiper__button:visited svg path { + fill: rgba(255, 255, 255, 0.95); +} +.root[data-is-touch=false] .swiper--dark .swiper__button:hover { + color: rgba(255, 255, 255, 0.95); + background-color: rgba(46, 46, 46, 0.4); +} +.root[data-is-touch=false] .swiper--dark .swiper__button:hover svg path { + fill: rgba(255, 255, 255, 0.95); +} +.root[data-is-touch] .swiper--dark .swiper__button.active, .root[data-is-touch] .swiper--dark .swiper__button:active { + color: rgba(255, 255, 255, 0.95); + background-color: rgba(87, 87, 87, 0.4); +} +.root[data-is-touch] .swiper--dark .swiper__button.active svg path, .root[data-is-touch] .swiper--dark .swiper__button:active svg path { + fill: rgba(255, 255, 255, 0.95); +} +.root[data-is-touch] .swiper--dark .swiper__button.focus { + color: default; + background-color: rgba(46, 46, 46, 0.4); + box-shadow: 0 0 0 2px rgba(46, 46, 46, 0.4); +} +.root[data-is-touch] .swiper--dark .swiper__button.focus svg path { + fill: default; +} +.swiper--dark .swiper__button.disabled, .swiper--dark .swiper__button:disabled { + color: rgba(255, 255, 255, 0.4) !important; + background-color: rgba(0, 0, 0, 0.4) !important; +} +.swiper--dark .swiper__button.disabled svg path, .swiper--dark .swiper__button:disabled svg path { + fill: rgba(255, 255, 255, 0.4) !important; +} + +.swiper__button--prev { + left: 10px; +} + +.swiper__button--next { + right: 10px; +} + +@-webkit-keyframes fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } +} +@keyframes fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } +} +@-webkit-keyframes fade-in-down { + from { + opacity: 0; + -webkit-transform: translateY(-2rem); + transform: translateY(-2rem); + } + to { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } +} +@keyframes fade-in-down { + from { + opacity: 0; + -webkit-transform: translateY(-2rem); + transform: translateY(-2rem); + } + to { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } +} +@-webkit-keyframes fade-in-up { + from { + opacity: 0; + -webkit-transform: translateY(2rem); + transform: translateY(2rem); + } + to { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } +} +@keyframes fade-in-up { + from { + opacity: 0; + -webkit-transform: translateY(2rem); + transform: translateY(2rem); + } + to { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } +} +.main { + width: 100%; + max-width: 950px; + padding: 0 3rem; + margin: 0 auto; +} +@media (max-width: 1023px) { + .main { + padding: 0 1.5rem; + } +} +@media (max-width: 499px) { + .main { + padding: 0 1rem; + } +} + +.has-aside .main { + max-width: 1170px; +} +@media (max-width: 1023px) { + .has-aside .main { + max-width: 950px; + } +} + +.full-width .main { + width: 100%; + max-width: 100%; +} + +.header { + background: #f2f2f2; +} +.header a { + font-weight: 400; + text-decoration: none !important; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.header a, .header a:link, .header a:visited { + text-decoration: none; +} +.root[data-is-touch=false] .header a:hover { + text-decoration: underline; +} +.root[data-is-touch] .header a.active, .root[data-is-touch] .header a:active { + text-decoration: none; +} +.header a, .header a:link, .header a:visited { + color: #333; +} +.root[data-is-touch=false] .header a:hover { + color: #fc4d50; +} +.root[data-is-touch] .header a.active, .root[data-is-touch] .header a:active { + color: #f80408; +} +.header a.disabled, .header a:disabled { + color: rgba(51, 51, 51, 0.2) !important; +} +.header .main { + display: -webkit-box; + display: -webkit-flex; + display: -moz-flex; + display: -ms-flexbox; + display: flex; +} +@media (max-width: 499px) { + .header .main { + -webkit-box-direction: normal; + -webkit-box-orient: vertical; + -webkit-flex-direction: column; + -moz-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + } +} + +.header--dark { + background: rgba(0, 0, 0, 0.15); +} +.header--dark .navigation__item--active::after { + border: 0 solid rgba(255, 255, 255, 0.95); + border-bottom-width: 4px; +} + +.header--light { + background: rgba(255, 255, 255, 0.15); +} +.header--light .navigation__item--active::after { + border: 0 solid #222; + border-bottom-width: 4px; +} + +.header__title { + display: -webkit-box; + display: -webkit-flex; + display: -moz-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-wrap: wrap; + -moz-flex-wrap: wrap; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin-top: 0; + margin-bottom: 0; + -webkit-box-align: center; + -ms-flex-align: center; + -webkit-align-items: center; + -moz-align-items: center; + align-items: center; + -webkit-flex-wrap: nowrap; + -moz-flex-wrap: nowrap; + -ms-flex-wrap: none; + flex-wrap: nowrap; + -webkit-box-flex: 1; + -webkit-flex: 1; + -moz-box-flex: 1; + -moz-flex: 1; + -ms-flex: 1; + flex: 1; + height: 5rem; + margin-right: 1rem; + white-space: nowrap; +} +.header__title > li { + margin-top: 0; + margin-bottom: 0; + margin-right: 1rem; + list-style-type: none; +} +.header__title > li:last-child { + margin-right: 0; +} +@media (max-width: 499px) { + .header__title { + height: auto; + margin-right: 0; + } +} +.header__title > .header__brand { + -webkit-box-flex: 1; + -webkit-flex: 1; + -moz-box-flex: 1; + -moz-flex: 1; + -ms-flex: 1; + flex: 1; +} +@media (max-width: 499px) { + .header__title > .header__brand { + height: 3rem; + } +} +.header__title > .search-button { + display: none; + margin-left: 0.5rem; +} +@media (max-width: 499px) { + .header__title > .search-button { + display: -webkit-box; + display: -webkit-flex; + display: -moz-flex; + display: -ms-flexbox; + display: flex; + } +} + +.header__brand { + display: -webkit-box; + display: -webkit-flex; + display: -moz-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + -webkit-align-items: center; + -moz-align-items: center; + align-items: center; +} +.header__brand > svg { + width: 1.92rem; + height: 1.92rem; + margin-right: 1rem; + vertical-align: middle; +} +@media (max-width: 499px) { + .header__brand > svg { + width: 1.44rem; + height: 1.44rem; + } +} +.header__brand > a { + display: inline-block; + font-size: 1.2rem; +} +.navigation { + overflow: hidden; + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} +.navigation > ul { + height: 5rem; + padding-bottom: 0; + margin: 0; + display: -webkit-box; + display: -webkit-flex; + display: -moz-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-wrap: nowrap; + -moz-flex-wrap: nowrap; + -ms-flex-wrap: none; + flex-wrap: nowrap; + margin-top: 0; + margin-bottom: 0; + -webkit-box-align: center; + -ms-flex-align: center; + -webkit-align-items: center; + -moz-align-items: center; + align-items: center; +} +@media (max-width: 499px) { + .navigation > ul { + padding-bottom: 4px; + margin: -4px 0 0 0; + } +} +.navigation > ul > li { + margin-top: 0.5rem; + margin-bottom: 0.5rem; + margin-right: 1rem; + list-style-type: none; +} +.navigation > ul > li:last-child { + margin-right: 0; +} +@media (max-width: 499px) { + .navigation > ul { + height: auto; + } +} +@media (max-width: 499px) { + .navigation > ul .search-button { + display: none; + } +} + +.navigation__item::after { + display: block; + margin-bottom: -4px; + content: ""; + border: 0 solid transparent; + border-bottom-width: 4px; +} + +.navigation__item--active a { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.navigation__item--active a, .navigation__item--active a:link, .navigation__item--active a:visited { + text-decoration: none; +} +.root[data-is-touch=false] .navigation__item--active a:hover { + text-decoration: underline; +} +.root[data-is-touch] .navigation__item--active a.active, .root[data-is-touch] .navigation__item--active a:active { + text-decoration: none; +} +.navigation__item--active a, .navigation__item--active a:link, .navigation__item--active a:visited { + color: #fc4d50; +} +.root[data-is-touch=false] .navigation__item--active a:hover { + color: #fc4d50; +} +.root[data-is-touch] .navigation__item--active a.active, .root[data-is-touch] .navigation__item--active a:active { + color: #f80408; +} +.navigation__item--active a.disabled, .navigation__item--active a:disabled { + color: rgba(252, 77, 80, 0.2) !important; +} +.navigation__item--active::after { + border: 0 solid #fc4d50; + border-bottom-width: 4px; +} + +/** + * Site Info + */ +.footer { + display: -webkit-box; + display: -webkit-flex; + display: -moz-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + -webkit-align-items: center; + -moz-align-items: center; + align-items: center; + color: #333; + background: #f2f2f2; +} +.footer a { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.footer a, .footer a:link, .footer a:visited { + text-decoration: none; +} +.root[data-is-touch=false] .footer a:hover { + text-decoration: underline; +} +.root[data-is-touch] .footer a.active, .root[data-is-touch] .footer a:active { + text-decoration: none; +} +.footer a, .footer a:link, .footer a:visited { + color: #333; +} +.root[data-is-touch=false] .footer a:hover { + color: #fc4d50; +} +.root[data-is-touch] .footer a.active, .root[data-is-touch] .footer a:active { + color: #f80408; +} +.footer a.disabled, .footer a:disabled { + color: rgba(51, 51, 51, 0.2) !important; +} +.footer .site-info { + font-size: 0.7rem; + text-align: center; +} +.footer .site-info .menu { + line-height: 1.2; +} +.footer .site-info .menu > *:not(:last-child) { + border: 0 solid #333; + border-right-width: 1px; + padding-right: 0.25rem; + margin-right: 0.25rem; +} + +.footer__author-links { + overflow: auto; + -webkit-overflow-scrolling: touch; +} +.footer__author-links .author-links { + text-align: center; +} + +.article-list .item__meta { + padding: 0 1rem 0 0; + font-family: Menlo, Monaco, Consolas, Andale Mono, lucida console, Courier New, monospace; + font-size: 0.85rem; + white-space: nowrap; +} +.article-list.grid--sm .card__header { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.article-list__group-header { + margin-top: 1rem; +} + +.article__info { + font-size: 0.85rem; + color: #888; +} +.article__info .left-col { + float: left; +} +@media (max-width: 499px) { + .article__info .left-col { + float: none; + } +} +.article__info .right-col { + float: right; + margin-left: 0.5rem; +} +@media (max-width: 499px) { + .article__info .right-col { + float: none; + } +} +.article__info .right-col > li:not(:last-child) { + border: 0 solid #888; + border-right-width: 1px; + padding-right: 0.5rem; + margin-right: 0.5rem; + line-height: 1.2; +} + +.article__header { + margin-top: 3rem; + margin-bottom: 1.5rem; +} +@media (max-width: 499px) { + .article__header { + margin-top: 1.5rem; + } +} +.article__header header, .article__header h1 { + display: inline; +} +.article__header h1 { + word-wrap: break-word; +} +.article__header .split-space { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.article__header .edit-on-github { + text-decoration: none !important; +} + +.article__header--overlay .overlay { + min-height: 36rem; + padding-top: 6rem; + padding-bottom: 6rem; +} +@media (max-width: 499px) { + .article__header--overlay .overlay { + min-height: 29rem; + padding-top: 3rem; + padding-bottom: 3rem; + } +} +.article__header--overlay .overlay__excerpt { + font-size: 2rem; + font-weight: 700; +} +@media (max-width: 1023px) { + .article__header--overlay .overlay__excerpt { + font-size: 1.75rem; + } +} +@media (max-width: 499px) { + .article__header--overlay .overlay__excerpt { + font-size: 1.35rem; + } +} +.article__header--overlay .article__header { + margin-top: 0; +} + +.article__header--cover { + width: 100%; +} + +.article__content { + line-height: 1.8; + word-wrap: break-word; +} +@media print { + .article__content { + line-height: 1.6; + } +} +.article__content h1, +.article__content h2, +.article__content h3, +.article__content h4, +.article__content h5, +.article__content h6, +.article__content p, +.article__content hr, +.article__content blockquote, +.article__content figure, +.article__content pre, +.article__content .highlighter-rouge, +.article__content ul, +.article__content ol, +.article__content dl, +.article__content table, +.article__content .footnotes { + margin: 1rem 0; +} +@media print { + .article__content h1, + .article__content h2, + .article__content h3, + .article__content h4, + .article__content h5, + .article__content h6, + .article__content p, + .article__content hr, + .article__content blockquote, + .article__content figure, + .article__content pre, + .article__content .highlighter-rouge, + .article__content ul, + .article__content ol, + .article__content dl, + .article__content table, + .article__content .footnotes { + margin: 0.5rem 0; + } +} +.article__content h1, .article__content h2, .article__content h3, .article__content h4, .article__content h5, .article__content h6 { + position: relative; + margin-top: 1.5rem; +} +@media print { + .article__content h1, .article__content h2, .article__content h3, .article__content h4, .article__content h5, .article__content h6 { + margin-top: 1rem; + } +} +.article__content h1 > .anchor, .article__content h2 > .anchor, .article__content h3 > .anchor, .article__content h4 > .anchor, .article__content h5 > .anchor, .article__content h6 > .anchor { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; + margin-left: 0.25rem; + text-decoration: none; + visibility: hidden; + opacity: 0; +} +.article__content h1 > .anchor, .article__content h1 > .anchor:link, .article__content h1 > .anchor:visited, .article__content h2 > .anchor, .article__content h2 > .anchor:link, .article__content h2 > .anchor:visited, .article__content h3 > .anchor, .article__content h3 > .anchor:link, .article__content h3 > .anchor:visited, .article__content h4 > .anchor, .article__content h4 > .anchor:link, .article__content h4 > .anchor:visited, .article__content h5 > .anchor, .article__content h5 > .anchor:link, .article__content h5 > .anchor:visited, .article__content h6 > .anchor, .article__content h6 > .anchor:link, .article__content h6 > .anchor:visited { + text-decoration: none; +} +.root[data-is-touch=false] .article__content h1 > .anchor:hover, .root[data-is-touch=false] .article__content h2 > .anchor:hover, .root[data-is-touch=false] .article__content h3 > .anchor:hover, .root[data-is-touch=false] .article__content h4 > .anchor:hover, .root[data-is-touch=false] .article__content h5 > .anchor:hover, .root[data-is-touch=false] .article__content h6 > .anchor:hover { + text-decoration: underline; +} +.root[data-is-touch] .article__content h1 > .anchor.active, .root[data-is-touch] .article__content h1 > .anchor:active, .root[data-is-touch] .article__content h2 > .anchor.active, .root[data-is-touch] .article__content h2 > .anchor:active, .root[data-is-touch] .article__content h3 > .anchor.active, .root[data-is-touch] .article__content h3 > .anchor:active, .root[data-is-touch] .article__content h4 > .anchor.active, .root[data-is-touch] .article__content h4 > .anchor:active, .root[data-is-touch] .article__content h5 > .anchor.active, .root[data-is-touch] .article__content h5 > .anchor:active, .root[data-is-touch] .article__content h6 > .anchor.active, .root[data-is-touch] .article__content h6 > .anchor:active { + text-decoration: none; +} +.article__content h1 > .anchor, .article__content h1 > .anchor:link, .article__content h1 > .anchor:visited, .article__content h2 > .anchor, .article__content h2 > .anchor:link, .article__content h2 > .anchor:visited, .article__content h3 > .anchor, .article__content h3 > .anchor:link, .article__content h3 > .anchor:visited, .article__content h4 > .anchor, .article__content h4 > .anchor:link, .article__content h4 > .anchor:visited, .article__content h5 > .anchor, .article__content h5 > .anchor:link, .article__content h5 > .anchor:visited, .article__content h6 > .anchor, .article__content h6 > .anchor:link, .article__content h6 > .anchor:visited { + color: #cccccc; +} +.root[data-is-touch=false] .article__content h1 > .anchor:hover, .root[data-is-touch=false] .article__content h2 > .anchor:hover, .root[data-is-touch=false] .article__content h3 > .anchor:hover, .root[data-is-touch=false] .article__content h4 > .anchor:hover, .root[data-is-touch=false] .article__content h5 > .anchor:hover, .root[data-is-touch=false] .article__content h6 > .anchor:hover { + color: #fc4d50; +} +.root[data-is-touch] .article__content h1 > .anchor.active, .root[data-is-touch] .article__content h1 > .anchor:active, .root[data-is-touch] .article__content h2 > .anchor.active, .root[data-is-touch] .article__content h2 > .anchor:active, .root[data-is-touch] .article__content h3 > .anchor.active, .root[data-is-touch] .article__content h3 > .anchor:active, .root[data-is-touch] .article__content h4 > .anchor.active, .root[data-is-touch] .article__content h4 > .anchor:active, .root[data-is-touch] .article__content h5 > .anchor.active, .root[data-is-touch] .article__content h5 > .anchor:active, .root[data-is-touch] .article__content h6 > .anchor.active, .root[data-is-touch] .article__content h6 > .anchor:active { + color: #f80408; +} +.article__content h1 > .anchor.disabled, .article__content h1 > .anchor:disabled, .article__content h2 > .anchor.disabled, .article__content h2 > .anchor:disabled, .article__content h3 > .anchor.disabled, .article__content h3 > .anchor:disabled, .article__content h4 > .anchor.disabled, .article__content h4 > .anchor:disabled, .article__content h5 > .anchor.disabled, .article__content h5 > .anchor:disabled, .article__content h6 > .anchor.disabled, .article__content h6 > .anchor:disabled { + color: rgba(204, 204, 204, 0.2) !important; +} +.article__content h1 > .anchor > i, .article__content h2 > .anchor > i, .article__content h3 > .anchor > i, .article__content h4 > .anchor > i, .article__content h5 > .anchor > i, .article__content h6 > .anchor > i { + font-size: 0.85rem; +} +.root[data-is-touch=false] .article__content h1:hover > .anchor, .root[data-is-touch=false] .article__content h2:hover > .anchor, .root[data-is-touch=false] .article__content h3:hover > .anchor, .root[data-is-touch=false] .article__content h4:hover > .anchor, .root[data-is-touch=false] .article__content h5:hover > .anchor, .root[data-is-touch=false] .article__content h6:hover > .anchor { + cursor: pointer; + visibility: visible; + opacity: 1; +} +.article__content h1, +.article__content h2 { + border: 0 solid #e6e6e6; + border-bottom-width: 1px; +} +.article__content hr { + border: none; +} +.article__content hr::before { + display: block; + font-size: 1.9rem; + color: #888; + text-align: center; + letter-spacing: 1.5rem; + content: "..."; +} +.article__content blockquote { + padding-left: 1rem; + font-size: 0.85rem; + color: #888; + border: 0 solid #cccccc; + border-left-width: 4px; +} +.article__content blockquote p { + margin: 0.5rem 0; +} +.article__content blockquote > :last-child { + margin-bottom: 0; +} +.article__content img:not(.emoji) { + max-width: 100%; + vertical-align: middle; +} +.article__content .emoji { + display: inline-block; + width: 1.26rem; + height: 1.26rem; + vertical-align: text-bottom; +} +.article__content .footnotes { + border: 0 solid #e6e6e6; + border-top-width: 1px; + margin-top: 3rem; +} +@media print { + .article__content .footnotes { + margin-top: 1rem; + } +} +.article__content code { + padding: 0.25rem 0.5rem; + background-color: rgba(0, 0, 0, 0.05); + border-radius: 0.4rem; +} +.article__content code span { + padding: 0; + margin: 0; +} +.article__content pre { + overflow: auto; + -webkit-overflow-scrolling: touch; +} +.article__content pre > code { + padding: 0; + word-wrap: normal; + background-color: transparent; +} +.article__content pre > code.language-mermaid, .article__content pre > code.language-chart { + display: none; +} +.article__content pre > code.language-mermaid svg, .article__content pre > code.language-chart svg { + width: 100%; +} +.article__content pre > code.language-mermaid[data-processed], .article__content pre > code.language-chart[data-processed] { + display: block; +} +.article__content .highlighter-rouge > .highlight > pre, .article__content figure.highlight > pre { + padding: 1rem 0 1rem 1rem; + margin: 0; + background-color: rgba(0, 0, 0, 0.05); + border-radius: 0.4rem; +} +.article__content .highlighter-rouge > .highlight > pre > code, .article__content figure.highlight > pre > code { + display: block; +} +.article__content figure.highlight::before { + display: block; + padding: 0.5rem 1rem 0.5rem 0; + font-weight: 700; + color: rgba(0, 0, 0, 0.1); + text-align: right; + text-transform: uppercase; + content: attr(data-lang); + background-color: rgba(0, 0, 0, 0.05); + border-top-left-radius: 0.4rem; + border-top-right-radius: 0.4rem; +} +.article__content figure.highlight > pre { + padding-top: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.article__content figure.highlight > pre > code > .rouge-table { + width: auto; + margin: 0 0 -1rem -1rem; +} +.article__content figure.highlight > pre > code > .rouge-table tbody, .article__content figure.highlight > pre > code > .rouge-table tr, .article__content figure.highlight > pre > code > .rouge-table td { + padding-top: 0; + padding-bottom: 0; + border: none; +} +.article__content figure.highlight > pre > code > .rouge-table > tbody { + display: -webkit-box; + display: -webkit-flex; + display: -moz-flex; + display: -ms-flexbox; + display: flex; +} +.article__content figure.highlight > pre > code > .rouge-table > tbody > tr { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -moz-flex; + display: -ms-flexbox; + display: flex; +} +.article__content figure.highlight > pre > code > .rouge-table > tbody > tr > .code { + padding: 0 0 1rem 0.5rem; + overflow: auto; + -webkit-overflow-scrolling: touch; +} +.article__content figure.highlight > pre > code > .rouge-table tbody td.gl { + padding-left: 1rem; +} +.article__content figure.highlight > pre > code > .rouge-table tbody td > pre { + display: block; + margin: 0; + border-radius: 0; + overflow: auto; + -webkit-overflow-scrolling: touch; +} +.article__content figure.highlight > pre > code > .rouge-table tbody td > pre.lineno { + color: #888; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.article__content ul, .article__content ol { + margin-left: 1.5rem; +} +.article__content ul ul, .article__content ul ol, .article__content ol ul, .article__content ol ol { + margin-top: 0; + margin-bottom: 0; +} +.article__content ul li p, .article__content ol li p { + margin: 0.5rem; +} +@media print { + .article__content ul li p, .article__content ol li p { + margin: 0.25rem; + } +} +.article__content dl dt p, .article__content dl dd p { + margin: 0.5rem; +} +@media print { + .article__content dl dt p, .article__content dl dd p { + margin: 0.25rem; + } +} +.article__content dl dt { + font-weight: 700; +} +.article__content dl dd { + margin-left: 2rem; +} +.article__content ul.task-list { + margin-left: 0; + list-style-type: none; +} +.article__content ul.task-list ul, .article__content ul.task-list ol { + margin-left: 1.5rem; +} +.article__content table { + display: block; + width: 100%; + border-collapse: collapse; + overflow: auto; + -webkit-overflow-scrolling: touch; +} +.article__content table thead, .article__content table tfoot { + background-color: rgba(0, 0, 0, 0.05); +} +.article__content table th, .article__content table td { + padding: 0.5rem; + border: 1px solid #e6e6e6; +} +.article__content table th { + font-weight: 700; +} + +.article__footer { + margin: 1.5rem 0; + font-size: 0.85rem; +} + +.article__license a, .article__subscribe a { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.article__license a, .article__license a:link, .article__license a:visited, .article__subscribe a, .article__subscribe a:link, .article__subscribe a:visited { + text-decoration: none; +} +.root[data-is-touch=false] .article__license a:hover, .root[data-is-touch=false] .article__subscribe a:hover { + text-decoration: underline; +} +.root[data-is-touch] .article__license a.active, .root[data-is-touch] .article__license a:active, .root[data-is-touch] .article__subscribe a.active, .root[data-is-touch] .article__subscribe a:active { + text-decoration: none; +} +.article__license a, .article__license a:link, .article__license a:visited, .article__subscribe a, .article__subscribe a:link, .article__subscribe a:visited { + color: #222; +} +.root[data-is-touch=false] .article__license a:hover, .root[data-is-touch=false] .article__subscribe a:hover { + color: #fc4d50; +} +.root[data-is-touch] .article__license a.active, .root[data-is-touch] .article__license a:active, .root[data-is-touch] .article__subscribe a.active, .root[data-is-touch] .article__subscribe a:active { + color: #f80408; +} +.article__license a.disabled, .article__license a:disabled, .article__subscribe a.disabled, .article__subscribe a:disabled { + color: rgba(34, 34, 34, 0.2) !important; +} + +.article__license { + color: #888; +} +.article__license img { + height: 1.6rem; +} + +.author-links > ul { + margin: 0; +} +.author-links > ul > li > .mail-button { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .mail-button svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .mail-button, .author-links > ul > li > .mail-button:link, .author-links > ul > li > .mail-button:visited { + color: #fff; + background-color: #0072c5; +} +.author-links > ul > li > .mail-button svg path, .author-links > ul > li > .mail-button:link svg path, .author-links > ul > li > .mail-button:visited svg path { + fill: #fff; +} +.root[data-is-touch=false] .author-links > ul > li > .mail-button:hover { + color: #fff; + background-color: #00497e; +} +.root[data-is-touch=false] .author-links > ul > li > .mail-button:hover svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .mail-button.active, .root[data-is-touch] .author-links > ul > li > .mail-button:active { + color: #fff; + background-color: #001c31; +} +.root[data-is-touch] .author-links > ul > li > .mail-button.active svg path, .root[data-is-touch] .author-links > ul > li > .mail-button:active svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .mail-button.focus { + color: default; + background-color: #00497e; + box-shadow: 0 0 0 2px rgba(0, 73, 126, 0.4); +} +.root[data-is-touch] .author-links > ul > li > .mail-button.focus svg path { + fill: default; +} +.author-links > ul > li > .mail-button.disabled, .author-links > ul > li > .mail-button:disabled { + color: rgba(255, 255, 255, 0.2) !important; + background-color: #0072c5 !important; +} +.author-links > ul > li > .mail-button.disabled svg path, .author-links > ul > li > .mail-button:disabled svg path { + fill: rgba(255, 255, 255, 0.2) !important; +} +.author-links > ul > li > .facebook-button { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .facebook-button svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .facebook-button, .author-links > ul > li > .facebook-button:link, .author-links > ul > li > .facebook-button:visited { + color: #fff; + background-color: #4267b2; +} +.author-links > ul > li > .facebook-button svg path, .author-links > ul > li > .facebook-button:link svg path, .author-links > ul > li > .facebook-button:visited svg path { + fill: #fff; +} +.root[data-is-touch=false] .author-links > ul > li > .facebook-button:hover { + color: #fff; + background-color: #2f497e; +} +.root[data-is-touch=false] .author-links > ul > li > .facebook-button:hover svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .facebook-button.active, .root[data-is-touch] .author-links > ul > li > .facebook-button:active { + color: #fff; + background-color: #1a2946; +} +.root[data-is-touch] .author-links > ul > li > .facebook-button.active svg path, .root[data-is-touch] .author-links > ul > li > .facebook-button:active svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .facebook-button.focus { + color: default; + background-color: #2f497e; + box-shadow: 0 0 0 2px rgba(47, 73, 126, 0.4); +} +.root[data-is-touch] .author-links > ul > li > .facebook-button.focus svg path { + fill: default; +} +.author-links > ul > li > .facebook-button.disabled, .author-links > ul > li > .facebook-button:disabled { + color: rgba(255, 255, 255, 0.2) !important; + background-color: #4267b2 !important; +} +.author-links > ul > li > .facebook-button.disabled svg path, .author-links > ul > li > .facebook-button:disabled svg path { + fill: rgba(255, 255, 255, 0.2) !important; +} +.author-links > ul > li > .twitter-button { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .twitter-button svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .twitter-button, .author-links > ul > li > .twitter-button:link, .author-links > ul > li > .twitter-button:visited { + color: #fff; + background-color: #1da1f2; +} +.author-links > ul > li > .twitter-button svg path, .author-links > ul > li > .twitter-button:link svg path, .author-links > ul > li > .twitter-button:visited svg path { + fill: #fff; +} +.root[data-is-touch=false] .author-links > ul > li > .twitter-button:hover { + color: #fff; + background-color: #0b79bd; +} +.root[data-is-touch=false] .author-links > ul > li > .twitter-button:hover svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .twitter-button.active, .root[data-is-touch] .author-links > ul > li > .twitter-button:active { + color: #fff; + background-color: #074b74; +} +.root[data-is-touch] .author-links > ul > li > .twitter-button.active svg path, .root[data-is-touch] .author-links > ul > li > .twitter-button:active svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .twitter-button.focus { + color: default; + background-color: #0b79bd; + box-shadow: 0 0 0 2px rgba(11, 121, 189, 0.4); +} +.root[data-is-touch] .author-links > ul > li > .twitter-button.focus svg path { + fill: default; +} +.author-links > ul > li > .twitter-button.disabled, .author-links > ul > li > .twitter-button:disabled { + color: rgba(255, 255, 255, 0.2) !important; + background-color: #1da1f2 !important; +} +.author-links > ul > li > .twitter-button.disabled svg path, .author-links > ul > li > .twitter-button:disabled svg path { + fill: rgba(255, 255, 255, 0.2) !important; +} +.author-links > ul > li > .weibo-button { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .weibo-button svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .weibo-button, .author-links > ul > li > .weibo-button:link, .author-links > ul > li > .weibo-button:visited { + color: #fff; + background-color: #e6162d; +} +.author-links > ul > li > .weibo-button svg path, .author-links > ul > li > .weibo-button:link svg path, .author-links > ul > li > .weibo-button:visited svg path { + fill: #fff; +} +.root[data-is-touch=false] .author-links > ul > li > .weibo-button:hover { + color: #fff; + background-color: #a51020; +} +.root[data-is-touch=false] .author-links > ul > li > .weibo-button:hover svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .weibo-button.active, .root[data-is-touch] .author-links > ul > li > .weibo-button:active { + color: #fff; + background-color: #5f0913; +} +.root[data-is-touch] .author-links > ul > li > .weibo-button.active svg path, .root[data-is-touch] .author-links > ul > li > .weibo-button:active svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .weibo-button.focus { + color: default; + background-color: #a51020; + box-shadow: 0 0 0 2px rgba(165, 16, 32, 0.4); +} +.root[data-is-touch] .author-links > ul > li > .weibo-button.focus svg path { + fill: default; +} +.author-links > ul > li > .weibo-button.disabled, .author-links > ul > li > .weibo-button:disabled { + color: rgba(255, 255, 255, 0.2) !important; + background-color: #e6162d !important; +} +.author-links > ul > li > .weibo-button.disabled svg path, .author-links > ul > li > .weibo-button:disabled svg path { + fill: rgba(255, 255, 255, 0.2) !important; +} +.author-links > ul > li > .googlepluse-button { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .googlepluse-button svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .googlepluse-button, .author-links > ul > li > .googlepluse-button:link, .author-links > ul > li > .googlepluse-button:visited { + color: #fff; + background-color: #ea4335; +} +.author-links > ul > li > .googlepluse-button svg path, .author-links > ul > li > .googlepluse-button:link svg path, .author-links > ul > li > .googlepluse-button:visited svg path { + fill: #fff; +} +.root[data-is-touch=false] .author-links > ul > li > .googlepluse-button:hover { + color: #fff; + background-color: #c32214; +} +.root[data-is-touch=false] .author-links > ul > li > .googlepluse-button:hover svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .googlepluse-button.active, .root[data-is-touch] .author-links > ul > li > .googlepluse-button:active { + color: #fff; + background-color: #7e160d; +} +.root[data-is-touch] .author-links > ul > li > .googlepluse-button.active svg path, .root[data-is-touch] .author-links > ul > li > .googlepluse-button:active svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .googlepluse-button.focus { + color: default; + background-color: #c32214; + box-shadow: 0 0 0 2px rgba(195, 34, 20, 0.4); +} +.root[data-is-touch] .author-links > ul > li > .googlepluse-button.focus svg path { + fill: default; +} +.author-links > ul > li > .googlepluse-button.disabled, .author-links > ul > li > .googlepluse-button:disabled { + color: rgba(255, 255, 255, 0.2) !important; + background-color: #ea4335 !important; +} +.author-links > ul > li > .googlepluse-button.disabled svg path, .author-links > ul > li > .googlepluse-button:disabled svg path { + fill: rgba(255, 255, 255, 0.2) !important; +} +.author-links > ul > li > .telegram-button { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .telegram-button svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .telegram-button, .author-links > ul > li > .telegram-button:link, .author-links > ul > li > .telegram-button:visited { + color: #fff; + background-color: #32afed; +} +.author-links > ul > li > .telegram-button svg path, .author-links > ul > li > .telegram-button:link svg path, .author-links > ul > li > .telegram-button:visited svg path { + fill: #fff; +} +.root[data-is-touch=false] .author-links > ul > li > .telegram-button:hover { + color: #fff; + background-color: #118ac6; +} +.root[data-is-touch=false] .author-links > ul > li > .telegram-button:hover svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .telegram-button.active, .root[data-is-touch] .author-links > ul > li > .telegram-button:active { + color: #fff; + background-color: #0b5980; +} +.root[data-is-touch] .author-links > ul > li > .telegram-button.active svg path, .root[data-is-touch] .author-links > ul > li > .telegram-button:active svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .telegram-button.focus { + color: default; + background-color: #118ac6; + box-shadow: 0 0 0 2px rgba(17, 138, 198, 0.4); +} +.root[data-is-touch] .author-links > ul > li > .telegram-button.focus svg path { + fill: default; +} +.author-links > ul > li > .telegram-button.disabled, .author-links > ul > li > .telegram-button:disabled { + color: rgba(255, 255, 255, 0.2) !important; + background-color: #32afed !important; +} +.author-links > ul > li > .telegram-button.disabled svg path, .author-links > ul > li > .telegram-button:disabled svg path { + fill: rgba(255, 255, 255, 0.2) !important; +} +.author-links > ul > li > .medium-button { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .medium-button svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .medium-button, .author-links > ul > li > .medium-button:link, .author-links > ul > li > .medium-button:visited { + color: #fff; + background-color: #000; +} +.author-links > ul > li > .medium-button svg path, .author-links > ul > li > .medium-button:link svg path, .author-links > ul > li > .medium-button:visited svg path { + fill: #fff; +} +.root[data-is-touch=false] .author-links > ul > li > .medium-button:hover { + color: #fff; + background-color: #2e2e2e; +} +.root[data-is-touch=false] .author-links > ul > li > .medium-button:hover svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .medium-button.active, .root[data-is-touch] .author-links > ul > li > .medium-button:active { + color: #fff; + background-color: #575757; +} +.root[data-is-touch] .author-links > ul > li > .medium-button.active svg path, .root[data-is-touch] .author-links > ul > li > .medium-button:active svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .medium-button.focus { + color: default; + background-color: #2e2e2e; + box-shadow: 0 0 0 2px rgba(46, 46, 46, 0.4); +} +.root[data-is-touch] .author-links > ul > li > .medium-button.focus svg path { + fill: default; +} +.author-links > ul > li > .medium-button.disabled, .author-links > ul > li > .medium-button:disabled { + color: rgba(255, 255, 255, 0.4) !important; + background-color: #000 !important; +} +.author-links > ul > li > .medium-button.disabled svg path, .author-links > ul > li > .medium-button:disabled svg path { + fill: rgba(255, 255, 255, 0.4) !important; +} +.author-links > ul > li > .zhihu-button { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .zhihu-button svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .zhihu-button, .author-links > ul > li > .zhihu-button:link, .author-links > ul > li > .zhihu-button:visited { + color: #fff; + background-color: #0084ff; +} +.author-links > ul > li > .zhihu-button svg path, .author-links > ul > li > .zhihu-button:link svg path, .author-links > ul > li > .zhihu-button:visited svg path { + fill: #fff; +} +.root[data-is-touch=false] .author-links > ul > li > .zhihu-button:hover { + color: #fff; + background-color: #005fb8; +} +.root[data-is-touch=false] .author-links > ul > li > .zhihu-button:hover svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .zhihu-button.active, .root[data-is-touch] .author-links > ul > li > .zhihu-button:active { + color: #fff; + background-color: #00376b; +} +.root[data-is-touch] .author-links > ul > li > .zhihu-button.active svg path, .root[data-is-touch] .author-links > ul > li > .zhihu-button:active svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .zhihu-button.focus { + color: default; + background-color: #005fb8; + box-shadow: 0 0 0 2px rgba(0, 95, 184, 0.4); +} +.root[data-is-touch] .author-links > ul > li > .zhihu-button.focus svg path { + fill: default; +} +.author-links > ul > li > .zhihu-button.disabled, .author-links > ul > li > .zhihu-button:disabled { + color: rgba(255, 255, 255, 0.2) !important; + background-color: #0084ff !important; +} +.author-links > ul > li > .zhihu-button.disabled svg path, .author-links > ul > li > .zhihu-button:disabled svg path { + fill: rgba(255, 255, 255, 0.2) !important; +} +.author-links > ul > li > .douban-button { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .douban-button svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .douban-button, .author-links > ul > li > .douban-button:link, .author-links > ul > li > .douban-button:visited { + color: #fff; + background-color: #42bd56; +} +.author-links > ul > li > .douban-button svg path, .author-links > ul > li > .douban-button:link svg path, .author-links > ul > li > .douban-button:visited svg path { + fill: #fff; +} +.root[data-is-touch=false] .author-links > ul > li > .douban-button:hover { + color: #fff; + background-color: #30883e; +} +.root[data-is-touch=false] .author-links > ul > li > .douban-button:hover svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .douban-button.active, .root[data-is-touch] .author-links > ul > li > .douban-button:active { + color: #fff; + background-color: #1c4f24; +} +.root[data-is-touch] .author-links > ul > li > .douban-button.active svg path, .root[data-is-touch] .author-links > ul > li > .douban-button:active svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .douban-button.focus { + color: default; + background-color: #30883e; + box-shadow: 0 0 0 2px rgba(48, 136, 62, 0.4); +} +.root[data-is-touch] .author-links > ul > li > .douban-button.focus svg path { + fill: default; +} +.author-links > ul > li > .douban-button.disabled, .author-links > ul > li > .douban-button:disabled { + color: rgba(255, 255, 255, 0.2) !important; + background-color: #42bd56 !important; +} +.author-links > ul > li > .douban-button.disabled svg path, .author-links > ul > li > .douban-button:disabled svg path { + fill: rgba(255, 255, 255, 0.2) !important; +} +.author-links > ul > li > .linkedin-button { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .linkedin-button svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .linkedin-button, .author-links > ul > li > .linkedin-button:link, .author-links > ul > li > .linkedin-button:visited { + color: #fff; + background-color: #1074af; +} +.author-links > ul > li > .linkedin-button svg path, .author-links > ul > li > .linkedin-button:link svg path, .author-links > ul > li > .linkedin-button:visited svg path { + fill: #fff; +} +.root[data-is-touch=false] .author-links > ul > li > .linkedin-button:hover { + color: #fff; + background-color: #0a496e; +} +.root[data-is-touch=false] .author-links > ul > li > .linkedin-button:hover svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .linkedin-button.active, .root[data-is-touch] .author-links > ul > li > .linkedin-button:active { + color: #fff; + background-color: #041a27; +} +.root[data-is-touch] .author-links > ul > li > .linkedin-button.active svg path, .root[data-is-touch] .author-links > ul > li > .linkedin-button:active svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .linkedin-button.focus { + color: default; + background-color: #0a496e; + box-shadow: 0 0 0 2px rgba(10, 73, 110, 0.4); +} +.root[data-is-touch] .author-links > ul > li > .linkedin-button.focus svg path { + fill: default; +} +.author-links > ul > li > .linkedin-button.disabled, .author-links > ul > li > .linkedin-button:disabled { + color: rgba(255, 255, 255, 0.2) !important; + background-color: #1074af !important; +} +.author-links > ul > li > .linkedin-button.disabled svg path, .author-links > ul > li > .linkedin-button:disabled svg path { + fill: rgba(255, 255, 255, 0.2) !important; +} +.author-links > ul > li > .github-button { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .github-button svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .github-button, .author-links > ul > li > .github-button:link, .author-links > ul > li > .github-button:visited { + color: #fff; + background-color: #000; +} +.author-links > ul > li > .github-button svg path, .author-links > ul > li > .github-button:link svg path, .author-links > ul > li > .github-button:visited svg path { + fill: #fff; +} +.root[data-is-touch=false] .author-links > ul > li > .github-button:hover { + color: #fff; + background-color: #2e2e2e; +} +.root[data-is-touch=false] .author-links > ul > li > .github-button:hover svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .github-button.active, .root[data-is-touch] .author-links > ul > li > .github-button:active { + color: #fff; + background-color: #575757; +} +.root[data-is-touch] .author-links > ul > li > .github-button.active svg path, .root[data-is-touch] .author-links > ul > li > .github-button:active svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .github-button.focus { + color: default; + background-color: #2e2e2e; + box-shadow: 0 0 0 2px rgba(46, 46, 46, 0.4); +} +.root[data-is-touch] .author-links > ul > li > .github-button.focus svg path { + fill: default; +} +.author-links > ul > li > .github-button.disabled, .author-links > ul > li > .github-button:disabled { + color: rgba(255, 255, 255, 0.4) !important; + background-color: #000 !important; +} +.author-links > ul > li > .github-button.disabled svg path, .author-links > ul > li > .github-button:disabled svg path { + fill: rgba(255, 255, 255, 0.4) !important; +} +.author-links > ul > li > .npm-button { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .npm-button svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-links > ul > li > .npm-button, .author-links > ul > li > .npm-button:link, .author-links > ul > li > .npm-button:visited { + color: #fff; + background-color: #fff; +} +.author-links > ul > li > .npm-button svg path, .author-links > ul > li > .npm-button:link svg path, .author-links > ul > li > .npm-button:visited svg path { + fill: #fff; +} +.root[data-is-touch=false] .author-links > ul > li > .npm-button:hover { + color: #fff; + background-color: #dbdbdb; +} +.root[data-is-touch=false] .author-links > ul > li > .npm-button:hover svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .npm-button.active, .root[data-is-touch] .author-links > ul > li > .npm-button:active { + color: #fff; + background-color: #b5b5b5; +} +.root[data-is-touch] .author-links > ul > li > .npm-button.active svg path, .root[data-is-touch] .author-links > ul > li > .npm-button:active svg path { + fill: #fff; +} +.root[data-is-touch] .author-links > ul > li > .npm-button.focus { + color: default; + background-color: #dbdbdb; + box-shadow: 0 0 0 2px rgba(219, 219, 219, 0.4); +} +.root[data-is-touch] .author-links > ul > li > .npm-button.focus svg path { + fill: default; +} +.author-links > ul > li > .npm-button.disabled, .author-links > ul > li > .npm-button:disabled { + color: rgba(255, 255, 255, 0.2) !important; + background-color: #fff !important; +} +.author-links > ul > li > .npm-button.disabled svg path, .author-links > ul > li > .npm-button:disabled svg path { + fill: rgba(255, 255, 255, 0.2) !important; +} + +.author-profile { + max-width: 25rem; + padding: 0.5rem 1rem; + margin: 1.5rem 0; + font-size: 0.85rem; + background-color: rgba(0, 0, 0, 0.05); +} +@media (max-width: 499px) { + .author-profile { + text-align: center; + } +} + +.author-profile__avatar { + width: 5rem; + height: 5rem; + margin-top: 0.5rem; + border-radius: 50%; +} + +.author-profile__name { + font-size: 1.25rem; + font-weight: 700; +} +.author-profile__name a { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.author-profile__name a, .author-profile__name a:link, .author-profile__name a:visited { + text-decoration: none; +} +.root[data-is-touch=false] .author-profile__name a:hover { + text-decoration: underline; +} +.root[data-is-touch] .author-profile__name a.active, .root[data-is-touch] .author-profile__name a:active { + text-decoration: none; +} +.author-profile__name a, .author-profile__name a:link, .author-profile__name a:visited { + color: #222; +} +.root[data-is-touch=false] .author-profile__name a:hover { + color: #fc4d50; +} +.root[data-is-touch] .author-profile__name a.active, .root[data-is-touch] .author-profile__name a:active { + color: #f80408; +} +.author-profile__name a.disabled, .author-profile__name a:disabled { + color: rgba(34, 34, 34, 0.2) !important; +} + +.author-profile__links { + overflow: auto; + -webkit-overflow-scrolling: touch; +} + +.site-tags .tag-button { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.site-tags .tag-button svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.site-tags .tag-button, .site-tags .tag-button:link, .site-tags .tag-button:visited { + color: #333; + background-color: #f2f2f2; +} +.site-tags .tag-button svg path, .site-tags .tag-button:link svg path, .site-tags .tag-button:visited svg path { + fill: #333; +} +.root[data-is-touch=false] .site-tags .tag-button:hover { + color: #333; + background-color: #cecece; +} +.root[data-is-touch=false] .site-tags .tag-button:hover svg path { + fill: #333; +} +.root[data-is-touch] .site-tags .tag-button.active, .root[data-is-touch] .site-tags .tag-button:active { + color: #fff; + background-color: #fca24d; +} +.root[data-is-touch] .site-tags .tag-button.active svg path, .root[data-is-touch] .site-tags .tag-button:active svg path { + fill: #fff; +} +.root[data-is-touch] .site-tags .tag-button.focus { + color: #fff; + background-color: #fca24d; + box-shadow: 0 0 0 2px rgba(252, 162, 77, 0.4); +} +.root[data-is-touch] .site-tags .tag-button.focus svg path { + fill: #fff; +} +.site-tags .tag-button.disabled, .site-tags .tag-button:disabled { + color: rgba(51, 51, 51, 0.2) !important; + background-color: #f2f2f2 !important; +} +.site-tags .tag-button.disabled svg path, .site-tags .tag-button:disabled svg path { + fill: rgba(51, 51, 51, 0.2) !important; +} +.site-tags .tag-button > .tag-button__count { + display: inline-block; + margin-left: 0.25rem; + font-size: 0.7rem; + line-height: 1; + vertical-align: top; +} +.site-tags .tag-button-1 { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.site-tags .tag-button-1 svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.site-tags .tag-button-1, .site-tags .tag-button-1:link, .site-tags .tag-button-1:visited { + color: #fff; + background-color: rgba(252, 77, 80, 0.4); +} +.site-tags .tag-button-1 svg path, .site-tags .tag-button-1:link svg path, .site-tags .tag-button-1:visited svg path { + fill: #fff; +} +.root[data-is-touch=false] .site-tags .tag-button-1:hover { + color: #fff; + background-color: rgba(251, 7, 11, 0.4); +} +.root[data-is-touch=false] .site-tags .tag-button-1:hover svg path { + fill: #fff; +} +.root[data-is-touch] .site-tags .tag-button-1.active, .root[data-is-touch] .site-tags .tag-button-1:active { + color: #fff; + background-color: #fca24d; +} +.root[data-is-touch] .site-tags .tag-button-1.active svg path, .root[data-is-touch] .site-tags .tag-button-1:active svg path { + fill: #fff; +} +.root[data-is-touch] .site-tags .tag-button-1.focus { + color: #fff; + background-color: #fca24d; + box-shadow: 0 0 0 2px rgba(252, 162, 77, 0.4); +} +.root[data-is-touch] .site-tags .tag-button-1.focus svg path { + fill: #fff; +} +.site-tags .tag-button-1.disabled, .site-tags .tag-button-1:disabled { + color: rgba(255, 255, 255, 0.2) !important; + background-color: rgba(252, 77, 80, 0.4) !important; +} +.site-tags .tag-button-1.disabled svg path, .site-tags .tag-button-1:disabled svg path { + fill: rgba(255, 255, 255, 0.2) !important; +} +.site-tags .tag-button-2 { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.site-tags .tag-button-2 svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.site-tags .tag-button-2, .site-tags .tag-button-2:link, .site-tags .tag-button-2:visited { + color: #fff; + background-color: rgba(252, 77, 80, 0.55); +} +.site-tags .tag-button-2 svg path, .site-tags .tag-button-2:link svg path, .site-tags .tag-button-2:visited svg path { + fill: #fff; +} +.root[data-is-touch=false] .site-tags .tag-button-2:hover { + color: #fff; + background-color: rgba(251, 7, 11, 0.55); +} +.root[data-is-touch=false] .site-tags .tag-button-2:hover svg path { + fill: #fff; +} +.root[data-is-touch] .site-tags .tag-button-2.active, .root[data-is-touch] .site-tags .tag-button-2:active { + color: #fff; + background-color: #fca24d; +} +.root[data-is-touch] .site-tags .tag-button-2.active svg path, .root[data-is-touch] .site-tags .tag-button-2:active svg path { + fill: #fff; +} +.root[data-is-touch] .site-tags .tag-button-2.focus { + color: #fff; + background-color: #fca24d; + box-shadow: 0 0 0 2px rgba(252, 162, 77, 0.4); +} +.root[data-is-touch] .site-tags .tag-button-2.focus svg path { + fill: #fff; +} +.site-tags .tag-button-2.disabled, .site-tags .tag-button-2:disabled { + color: rgba(255, 255, 255, 0.2) !important; + background-color: rgba(252, 77, 80, 0.55) !important; +} +.site-tags .tag-button-2.disabled svg path, .site-tags .tag-button-2:disabled svg path { + fill: rgba(255, 255, 255, 0.2) !important; +} +.site-tags .tag-button-3 { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.site-tags .tag-button-3 svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.site-tags .tag-button-3, .site-tags .tag-button-3:link, .site-tags .tag-button-3:visited { + color: #fff; + background-color: rgba(252, 77, 80, 0.7); +} +.site-tags .tag-button-3 svg path, .site-tags .tag-button-3:link svg path, .site-tags .tag-button-3:visited svg path { + fill: #fff; +} +.root[data-is-touch=false] .site-tags .tag-button-3:hover { + color: #fff; + background-color: rgba(251, 7, 11, 0.7); +} +.root[data-is-touch=false] .site-tags .tag-button-3:hover svg path { + fill: #fff; +} +.root[data-is-touch] .site-tags .tag-button-3.active, .root[data-is-touch] .site-tags .tag-button-3:active { + color: #fff; + background-color: #fca24d; +} +.root[data-is-touch] .site-tags .tag-button-3.active svg path, .root[data-is-touch] .site-tags .tag-button-3:active svg path { + fill: #fff; +} +.root[data-is-touch] .site-tags .tag-button-3.focus { + color: #fff; + background-color: #fca24d; + box-shadow: 0 0 0 2px rgba(252, 162, 77, 0.4); +} +.root[data-is-touch] .site-tags .tag-button-3.focus svg path { + fill: #fff; +} +.site-tags .tag-button-3.disabled, .site-tags .tag-button-3:disabled { + color: rgba(255, 255, 255, 0.2) !important; + background-color: rgba(252, 77, 80, 0.7) !important; +} +.site-tags .tag-button-3.disabled svg path, .site-tags .tag-button-3:disabled svg path { + fill: rgba(255, 255, 255, 0.2) !important; +} +.site-tags .tag-button-4 { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.site-tags .tag-button-4 svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.site-tags .tag-button-4, .site-tags .tag-button-4:link, .site-tags .tag-button-4:visited { + color: #fff; + background-color: rgba(252, 77, 80, 0.9); +} +.site-tags .tag-button-4 svg path, .site-tags .tag-button-4:link svg path, .site-tags .tag-button-4:visited svg path { + fill: #fff; +} +.root[data-is-touch=false] .site-tags .tag-button-4:hover { + color: #fff; + background-color: rgba(251, 7, 11, 0.9); +} +.root[data-is-touch=false] .site-tags .tag-button-4:hover svg path { + fill: #fff; +} +.root[data-is-touch] .site-tags .tag-button-4.active, .root[data-is-touch] .site-tags .tag-button-4:active { + color: #fff; + background-color: #fca24d; +} +.root[data-is-touch] .site-tags .tag-button-4.active svg path, .root[data-is-touch] .site-tags .tag-button-4:active svg path { + fill: #fff; +} +.root[data-is-touch] .site-tags .tag-button-4.focus { + color: #fff; + background-color: #fca24d; + box-shadow: 0 0 0 2px rgba(252, 162, 77, 0.4); +} +.root[data-is-touch] .site-tags .tag-button-4.focus svg path { + fill: #fff; +} +.site-tags .tag-button-4.disabled, .site-tags .tag-button-4:disabled { + color: rgba(255, 255, 255, 0.2) !important; + background-color: rgba(252, 77, 80, 0.9) !important; +} +.site-tags .tag-button-4.disabled svg path, .site-tags .tag-button-4:disabled svg path { + fill: rgba(255, 255, 255, 0.2) !important; +} + +.search { + overflow: auto; + -webkit-overflow-scrolling: touch; +} + +.search--google-custom-search-engine .main { + padding-top: 1.5rem; + padding-bottom: 1.5rem; +} +@media (max-width: 499px) { + .search--google-custom-search-engine .main { + position: absolute; + padding: 0; + } +} + +.search__header { + margin-top: 1.5rem; + font-size: 2.5rem; + font-weight: 700; + color: #000; +} +.search--light .search__header { + color: #000; +} +.search--dark .search__header { + color: #fff; +} +@media (max-width: 499px) { + .search__header { + display: none; + } +} + +.search-bar { + display: -webkit-box; + display: -webkit-flex; + display: -moz-flex; + display: -ms-flexbox; + display: flex; + margin: 1rem 0 1.5rem 0; +} + +.search-box { + position: relative; + width: 100%; + max-width: 22rem; +} +@media (max-width: 499px) { + .search-box { + width: 100%; + max-width: none; + } +} +.search-box > input { + display: inline-block; + width: 100%; + height: 2.3rem; + padding: 0 2rem; + margin: 0; + line-height: 1 !important; + color: #222; + background-color: transparent; + border: 2px solid #cccccc; + border-radius: 6rem; + -webkit-appearance: none; /* fix iOS don't display box-shadow properly */ + -webkit-transition: box-shadow 0.4s ease-in-out; + transition: box-shadow 0.4s ease-in-out; +} +.root[data-is-touch] .search-box > input.focus { + box-shadow: 0 0 0 2px rgba(204, 204, 204, 0.4); +} +.search--light .search-box > input { + color: #222; + border-color: #222; +} +.root[data-is-touch] .search--light .search-box > input.focus { + box-shadow: 0 0 0 2px rgba(34, 34, 34, 0.4); +} +.search--dark .search-box > input { + color: rgba(255, 255, 255, 0.95); + border-color: rgba(255, 255, 255, 0.95); +} +.root[data-is-touch] .search--dark .search-box > input.focus { + box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.4); +} +.search-box > .search-box__icon-search { + color: #888; +} +.search--light .search-box > .search-box__icon-search { + color: #888; +} +.search--dark .search-box > .search-box__icon-search { + color: rgba(255, 255, 255, 0.85); +} +.search-box > .search-box__icon-clear > a { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; + cursor: pointer; +} +.search-box > .search-box__icon-clear > a, .search-box > .search-box__icon-clear > a:link, .search-box > .search-box__icon-clear > a:visited { + text-decoration: none; +} +.root[data-is-touch=false] .search-box > .search-box__icon-clear > a:hover { + text-decoration: underline; +} +.root[data-is-touch] .search-box > .search-box__icon-clear > a.active, .root[data-is-touch] .search-box > .search-box__icon-clear > a:active { + text-decoration: none; +} +.search-box > .search-box__icon-clear > a, .search-box > .search-box__icon-clear > a:link, .search-box > .search-box__icon-clear > a:visited { + color: #222; +} +.root[data-is-touch=false] .search-box > .search-box__icon-clear > a:hover { + color: #505050; +} +.root[data-is-touch] .search-box > .search-box__icon-clear > a.active, .root[data-is-touch] .search-box > .search-box__icon-clear > a:active { + color: #797979; +} +.search-box > .search-box__icon-clear > a.disabled, .search-box > .search-box__icon-clear > a:disabled { + color: rgba(34, 34, 34, 0.4) !important; +} +.search--light .search-box > .search-box__icon-clear > a { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.search--light .search-box > .search-box__icon-clear > a, .search--light .search-box > .search-box__icon-clear > a:link, .search--light .search-box > .search-box__icon-clear > a:visited { + text-decoration: none; +} +.root[data-is-touch=false] .search--light .search-box > .search-box__icon-clear > a:hover { + text-decoration: underline; +} +.root[data-is-touch] .search--light .search-box > .search-box__icon-clear > a.active, .root[data-is-touch] .search--light .search-box > .search-box__icon-clear > a:active { + text-decoration: none; +} +.search--light .search-box > .search-box__icon-clear > a, .search--light .search-box > .search-box__icon-clear > a:link, .search--light .search-box > .search-box__icon-clear > a:visited { + color: #222; +} +.root[data-is-touch=false] .search--light .search-box > .search-box__icon-clear > a:hover { + color: #505050; +} +.root[data-is-touch] .search--light .search-box > .search-box__icon-clear > a.active, .root[data-is-touch] .search--light .search-box > .search-box__icon-clear > a:active { + color: #797979; +} +.search--light .search-box > .search-box__icon-clear > a.disabled, .search--light .search-box > .search-box__icon-clear > a:disabled { + color: rgba(34, 34, 34, 0.4) !important; +} +.search--dark .search-box > .search-box__icon-clear > a { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.search--dark .search-box > .search-box__icon-clear > a, .search--dark .search-box > .search-box__icon-clear > a:link, .search--dark .search-box > .search-box__icon-clear > a:visited { + text-decoration: none; +} +.root[data-is-touch=false] .search--dark .search-box > .search-box__icon-clear > a:hover { + text-decoration: underline; +} +.root[data-is-touch] .search--dark .search-box > .search-box__icon-clear > a.active, .root[data-is-touch] .search--dark .search-box > .search-box__icon-clear > a:active { + text-decoration: none; +} +.search--dark .search-box > .search-box__icon-clear > a, .search--dark .search-box > .search-box__icon-clear > a:link, .search--dark .search-box > .search-box__icon-clear > a:visited { + color: rgba(255, 255, 255, 0.95); +} +.root[data-is-touch=false] .search--dark .search-box > .search-box__icon-clear > a:hover { + color: rgba(219, 219, 219, 0.95); +} +.root[data-is-touch] .search--dark .search-box > .search-box__icon-clear > a.active, .root[data-is-touch] .search--dark .search-box > .search-box__icon-clear > a:active { + color: rgba(181, 181, 181, 0.95); +} +.search--dark .search-box > .search-box__icon-clear > a.disabled, .search--dark .search-box > .search-box__icon-clear > a:disabled { + color: rgba(255, 255, 255, 0.2) !important; +} +.search-box > .search-box__icon-search, .search-box > .search-box__icon-clear { + position: absolute; + width: 2.3rem; + height: 2.3rem; + line-height: 2.3rem; + text-align: center; + vertical-align: middle; +} +.search-box.not-empty > .search-box__icon-clear { + display: block; +} +.search-box > .search-box__icon-clear { + top: 0; + right: 0; + display: none; +} +.search-box > .search-box__icon-search { + top: 0; + left: 0; +} + +.search__cancel { + margin-left: 0.5rem; + font-weight: 700; + white-space: nowrap; +} + +.search-result { + margin: 1.5rem 0; + font-size: 0.85rem; + line-height: 1.4; +} + +.search-result__header { + margin: 1rem 0 0.5rem 0; + font-size: 1.25rem; + font-weight: 700; + color: #888; + text-transform: uppercase; +} +.search--light .search-result__header { + color: #888; +} +.search--dark .search-result__header { + color: rgba(255, 255, 255, 0.85); +} + +.search-result__item { + list-style-type: none; +} +.search-result__item a { + padding: 0.25rem 1rem; + -webkit-transition: none; + transition: none; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.search-result__item a svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.search-result__item a, .search-result__item a:link, .search-result__item a:visited { + color: #222; + background-color: transparent; +} +.search-result__item a svg path, .search-result__item a:link svg path, .search-result__item a:visited svg path { + fill: #222; +} +.root[data-is-touch=false] .search-result__item a:hover { + color: #333; + background-color: #f2f2f2; +} +.root[data-is-touch=false] .search-result__item a:hover svg path { + fill: #333; +} +.root[data-is-touch] .search-result__item a.active, .root[data-is-touch] .search-result__item a:active { + color: #333; + background-color: #cccccc; +} +.root[data-is-touch] .search-result__item a.active svg path, .root[data-is-touch] .search-result__item a:active svg path { + fill: #333; +} +.root[data-is-touch] .search-result__item a.focus { + color: default; + background-color: #f2f2f2; + box-shadow: 0 0 0 2px rgba(242, 242, 242, 0.4); +} +.root[data-is-touch] .search-result__item a.focus svg path { + fill: default; +} +.search-result__item a.disabled, .search-result__item a:disabled { + color: rgba(34, 34, 34, 0.2) !important; + background-color: transparent !important; +} +.search-result__item a.disabled svg path, .search-result__item a:disabled svg path { + fill: rgba(34, 34, 34, 0.2) !important; +} +.search--light .search-result__item a { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.search--light .search-result__item a svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.search--light .search-result__item a, .search--light .search-result__item a:link, .search--light .search-result__item a:visited { + color: #222; + background-color: transparent; +} +.search--light .search-result__item a svg path, .search--light .search-result__item a:link svg path, .search--light .search-result__item a:visited svg path { + fill: #222; +} +.root[data-is-touch=false] .search--light .search-result__item a:hover { + color: rgba(255, 255, 255, 0.95); + background-color: rgba(0, 0, 0, 0.9); +} +.root[data-is-touch=false] .search--light .search-result__item a:hover svg path { + fill: rgba(255, 255, 255, 0.95); +} +.root[data-is-touch] .search--light .search-result__item a.active, .root[data-is-touch] .search--light .search-result__item a:active { + color: rgba(255, 255, 255, 0.95); + background-color: rgba(41, 41, 41, 0.9); +} +.root[data-is-touch] .search--light .search-result__item a.active svg path, .root[data-is-touch] .search--light .search-result__item a:active svg path { + fill: rgba(255, 255, 255, 0.95); +} +.root[data-is-touch] .search--light .search-result__item a.focus { + color: default; + background-color: rgba(0, 0, 0, 0.9); + box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.4); +} +.root[data-is-touch] .search--light .search-result__item a.focus svg path { + fill: default; +} +.search--light .search-result__item a.disabled, .search--light .search-result__item a:disabled { + color: rgba(34, 34, 34, 0.4) !important; + background-color: transparent !important; +} +.search--light .search-result__item a.disabled svg path, .search--light .search-result__item a:disabled svg path { + fill: rgba(34, 34, 34, 0.4) !important; +} +.search--dark .search-result__item a { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.search--dark .search-result__item a svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.search--dark .search-result__item a, .search--dark .search-result__item a:link, .search--dark .search-result__item a:visited { + color: rgba(255, 255, 255, 0.95); + background-color: transparent; +} +.search--dark .search-result__item a svg path, .search--dark .search-result__item a:link svg path, .search--dark .search-result__item a:visited svg path { + fill: rgba(255, 255, 255, 0.95); +} +.root[data-is-touch=false] .search--dark .search-result__item a:hover { + color: #222; + background-color: rgba(255, 255, 255, 0.9); +} +.root[data-is-touch=false] .search--dark .search-result__item a:hover svg path { + fill: #222; +} +.root[data-is-touch] .search--dark .search-result__item a.active, .root[data-is-touch] .search--dark .search-result__item a:active { + color: #222; + background-color: rgba(217, 217, 217, 0.9); +} +.root[data-is-touch] .search--dark .search-result__item a.active svg path, .root[data-is-touch] .search--dark .search-result__item a:active svg path { + fill: #222; +} +.root[data-is-touch] .search--dark .search-result__item a.focus { + color: default; + background-color: rgba(255, 255, 255, 0.9); + box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.4); +} +.root[data-is-touch] .search--dark .search-result__item a.focus svg path { + fill: default; +} +.search--dark .search-result__item a.disabled, .search--dark .search-result__item a:disabled { + color: rgba(255, 255, 255, 0.2) !important; + background-color: transparent !important; +} +.search--dark .search-result__item a.disabled svg path, .search--dark .search-result__item a:disabled svg path { + fill: rgba(255, 255, 255, 0.2) !important; +} +.search-result__item.active a, .search-result__item.active a:link, .search-result__item.active a:visited { + color: #333; + background-color: #f2f2f2; +} +.search--light .search-result__item.active a, .search--light .search-result__item.active a:link, .search--light .search-result__item.active a:visited { + color: rgba(255, 255, 255, 0.95); + background-color: rgba(0, 0, 0, 0.9); +} +.search--dark .search-result__item.active a, .search--dark .search-result__item.active a:link, .search--dark .search-result__item.active a:visited { + color: #222; + background-color: rgba(255, 255, 255, 0.9); +} +.root[data-is-touch] .search-result__item.active a.active, .root[data-is-touch] .search-result__item.active a:active { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} + +.gsc-control-cse *, +.gsc-control-cse ::before, +.gsc-control-cse ::after { + box-sizing: initial; +} + +.popup-image { + cursor: pointer; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.root[data-is-touch=false] .popup-image:hover { + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.23), 0 2px 6px rgba(0, 0, 0, 0.08), 0 12px 24px rgba(0, 0, 0, 0.02); +} + +.extensions { + margin: 1rem 0; +} + +.extensions--video, .extensions--slide, .extensions--demo { + position: relative; + width: 100%; + padding: 0; +} +.extensions--video > iframe, .extensions--slide > iframe, .extensions--demo > iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.extensions--video { + padding-top: 56.25%; +} + +.extensions--slide { + padding-top: 81.3021702838%; +} + +.extensions--demo { + min-height: 340px; + padding-top: 56.25%; +} + +.extensions--audio { + display: block; + max-width: 100% !important; +} + +.article__content p.success { + padding: 0.5rem 1rem; + background-color: rgba(82, 196, 26, 0.1); + border: 1px solid #52c41a; + border-radius: 0.4rem; +} +.article__content p.info { + padding: 0.5rem 1rem; + background-color: rgba(24, 144, 255, 0.1); + border: 1px solid #1890ff; + border-radius: 0.4rem; +} +.article__content p.warning { + padding: 0.5rem 1rem; + background-color: rgba(250, 140, 22, 0.1); + border: 1px solid #fa8c16; + border-radius: 0.4rem; +} +.article__content p.error { + padding: 0.5rem 1rem; + background-color: rgba(245, 34, 45, 0.1); + border: 1px solid #f5222d; + border-radius: 0.4rem; +} + +.article__content code.success { + color: #fff; + background-color: #52c41a; +} +.article__content code.info { + color: #fff; + background-color: #1890ff; +} +.article__content code.warning { + color: #fff; + background-color: #fa8c16; +} +.article__content code.error { + color: #fff; + background-color: #f5222d; +} + +.article__content img.shadow, .article__content .shadow > img { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.23), 0 1px 3px rgba(0, 0, 0, 0.08), 0 6px 12px rgba(0, 0, 0, 0.02); +} +.article__content img.border, .article__content .border > img { + border: 1px solid #e6e6e6; +} +.article__content img.rounded, .article__content .rounded > img { + border-radius: 0.4rem; +} +.article__content img.circle, .article__content .circle > img { + border-radius: 50%; +} + +.icon { + display: block; +} +.icon > svg { + display: block; +} + +body, +html, +.root, +.layout--page { + height: 100%; +} + +.layout--page.layout--page--sidebar .page__viewport, +.layout--page.layout--page--sidebar .page__grid { + height: 100%; +} +@media (max-width: 1023px) { + .layout--page.layout--page--sidebar .page__main { + overflow: unset; + } +} + +.page__main { + height: 100%; + color: #222; +} +.page__main .col-aside { + display: none; +} +.page__main .col-aside > aside { + position: absolute; + width: 220px; + overflow: hidden; +} + +.page__main-inner { + position: relative; + display: -webkit-box; + display: -webkit-flex; + display: -moz-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-direction: normal; + -webkit-box-orient: vertical; + -webkit-flex-direction: column; + -moz-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + min-height: 100%; + background-color: #fff; +} + +.page__content { + -webkit-box-flex: 1; + -webkit-flex: 1; + -moz-box-flex: 1; + -moz-flex: 1; + -ms-flex: 1; + flex: 1; + width: 100%; + margin: 0 auto; +} +@media print { + .page__content { + padding-bottom: 0; + } +} + +.hide-footer .page__content { + padding-bottom: 0; +} + +.page__comments { + margin: 1.5rem 0; +} + +.page__aside .toc-aside { + padding: 3rem 0 1rem 3rem; +} + +.page__actions { + position: fixed; + bottom: 3rem; + left: 1rem; + z-index: 996; + display: none; +} + +.page__sidebar { + z-index: 998; + display: block; + width: 80%; + max-width: 250px; + height: 100%; + background-color: #fff; + border: 0 solid #e6e6e6; + border-right-width: 1px; + -webkit-transition: transform 0.4s; + transition: transform 0.4s; + overflow: auto; + -webkit-overflow-scrolling: touch; +} +.page__sidebar .sidebar-toc { + padding: 1rem 1rem 1.5rem 1.5rem; +} + +.sidebar-button { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.sidebar-button svg path { + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.sidebar-button, .sidebar-button:link, .sidebar-button:visited { + color: #000; + background-color: rgba(242, 242, 242, 0.75); +} +.sidebar-button svg path, .sidebar-button:link svg path, .sidebar-button:visited svg path { + fill: #000; +} +.root[data-is-touch=false] .sidebar-button:hover { + color: #000; + background-color: rgba(206, 206, 206, 0.75); +} +.root[data-is-touch=false] .sidebar-button:hover svg path { + fill: #000; +} +.root[data-is-touch] .sidebar-button.active, .root[data-is-touch] .sidebar-button:active { + color: #000; + background-color: rgba(168, 168, 168, 0.75); +} +.root[data-is-touch] .sidebar-button.active svg path, .root[data-is-touch] .sidebar-button:active svg path { + fill: #000; +} +.root[data-is-touch] .sidebar-button.focus { + color: default; + background-color: rgba(206, 206, 206, 0.75); + box-shadow: 0 0 0 2px rgba(206, 206, 206, 0.4); +} +.root[data-is-touch] .sidebar-button.focus svg path { + fill: default; +} +.sidebar-button.disabled, .sidebar-button:disabled { + color: rgba(0, 0, 0, 0.2) !important; + background-color: rgba(242, 242, 242, 0.75) !important; +} +.sidebar-button.disabled svg path, .sidebar-button:disabled svg path { + fill: rgba(0, 0, 0, 0.2) !important; +} + +.page__mask { + position: fixed; + top: 0; + left: 0; + z-index: 997; + width: 100%; + height: 100%; + color: rgba(255, 255, 255, 0.95); + touch-action: none; + background-color: rgba(0, 0, 0, 0.9); + opacity: 0; + -webkit-transform: translate(100%, 0); + transform: translate(100%, 0); + -webkit-transition: opacity 0.4s ease-in-out, transform 0s 0.4s ease-in-out; + transition: opacity 0.4s ease-in-out, transform 0s 0.4s ease-in-out; + cursor: pointer; +} + +.layout--page--sidebar .page__main { + overflow: auto; + -webkit-overflow-scrolling: touch; +} +@media print { + .layout--page--sidebar .page__main { + overflow: unset; + } +} + +.has-aside .col-aside { + position: relative; + display: block; + width: 220px; +} +.has-aside .col-aside > aside.fixed { + position: fixed; + -webkit-font-smoothing: subpixel-antialiased; +} +@media (max-width: 1023px) { + .has-aside .col-aside { + display: none; + } +} + +@media (max-width: 1023px) { + .page__sidebar { + position: fixed; + -webkit-transform: translate(-250px, 0); + transform: translate(-250px, 0); + } + .page__actions { + display: block; + } + .show-sidebar .page__actions { + visibility: hidden; + } + .show-sidebar .page__sidebar { + -webkit-transform: translate(0); + transform: translate(0); + } + .show-sidebar .page__mask { + opacity: 1; + -webkit-transform: translate(0, 0); + transform: translate(0, 0); + -webkit-transition: opacity 0.4s ease-in-out; + transition: opacity 0.4s ease-in-out; + } +} +.hero--light .article__info { + color: #222; +} + +.hero--dark .article__info { + color: rgba(255, 255, 255, 0.95); +} + +.page__main--immersive .page__header { + position: absolute; + width: 100%; +} +.page__main--immersive .hero__content { + padding-top: 5rem; +} + +.article__sharing { + margin: 1.5rem 0; +} + +.article__section-navigator { + padding-top: 1rem; + margin: 1.5rem 0 1rem 0; + word-wrap: break-word; + border: 0 solid #e6e6e6; + border-top-width: 4px; +} +.article__section-navigator > .previous, .article__section-navigator > .next { + width: 50%; +} +.article__section-navigator > .previous > span, .article__section-navigator > .next > span { + font-weight: 700; + color: #888; +} +.article__section-navigator > .previous > a, .article__section-navigator > .next > a { + display: block; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.article__section-navigator > .previous > a, .article__section-navigator > .previous > a:link, .article__section-navigator > .previous > a:visited, .article__section-navigator > .next > a, .article__section-navigator > .next > a:link, .article__section-navigator > .next > a:visited { + text-decoration: none; +} +.root[data-is-touch=false] .article__section-navigator > .previous > a:hover, .root[data-is-touch=false] .article__section-navigator > .next > a:hover { + text-decoration: underline; +} +.root[data-is-touch] .article__section-navigator > .previous > a.active, .root[data-is-touch] .article__section-navigator > .previous > a:active, .root[data-is-touch] .article__section-navigator > .next > a.active, .root[data-is-touch] .article__section-navigator > .next > a:active { + text-decoration: none; +} +.article__section-navigator > .previous > a, .article__section-navigator > .previous > a:link, .article__section-navigator > .previous > a:visited, .article__section-navigator > .next > a, .article__section-navigator > .next > a:link, .article__section-navigator > .next > a:visited { + color: #222; +} +.root[data-is-touch=false] .article__section-navigator > .previous > a:hover, .root[data-is-touch=false] .article__section-navigator > .next > a:hover { + color: #fc4d50; +} +.root[data-is-touch] .article__section-navigator > .previous > a.active, .root[data-is-touch] .article__section-navigator > .previous > a:active, .root[data-is-touch] .article__section-navigator > .next > a.active, .root[data-is-touch] .article__section-navigator > .next > a:active { + color: #f80408; +} +.article__section-navigator > .previous > a.disabled, .article__section-navigator > .previous > a:disabled, .article__section-navigator > .next > a.disabled, .article__section-navigator > .next > a:disabled { + color: rgba(34, 34, 34, 0.2) !important; +} +.article__section-navigator > .previous { + float: left; + padding-right: 0.5rem; +} +.article__section-navigator > .next { + float: right; + padding-left: 0.5rem; + text-align: right; +} + +.layout--articles { + margin: 1.5rem 0; + margin-top: 3rem; +} +@media (max-width: 499px) { + .layout--articles { + margin-top: 1.5rem; + } +} +.layout--articles .card__header { + font-size: 1rem; +} +.layout--articles .card__image > .overlay, .layout--articles .card__image > .overlay .card__header { + font-size: 0.85rem; +} + +.layout--archive > .layout--archive__result { + margin: 1.5rem 0; +} + +.layout--home .pagination { + margin: 1.5rem 0; +} +.layout--home .pagination__menu { + max-width: 100%; + overflow: auto; + -webkit-overflow-scrolling: touch; +} +.layout--home .pagination__omit { + color: #888; +} +.layout--home .items { + margin-top: 2.25rem; +} + +.layout--landing .heros { + max-width: 1900px; + margin-right: auto; + margin-left: auto; +} +.layout--landing .hero img { + display: block; + width: 100%; + margin: 0 auto; +} +.layout--landing .hero__content { + margin-bottom: 0; +} +.layout--landing .hero__cover { + max-width: 950px; +} +.layout--landing .hero__cover--full-width { + max-width: none; +} + +.layout--404 .sign { + display: table; + margin: 1.5rem auto; + margin-top: 3rem; +} +.layout--404 .sign h1 { + font-size: 6rem; + line-height: 1; +} +.layout--404 .sign p { + font-size: 1.8rem; +} + +/* start custom scss snippet */ +/* end custom scss snippet */ + +/*# sourceMappingURL=main.css.map */ \ No newline at end of file diff --git a/assets/css/main.css.map b/assets/css/main.css.map new file mode 100644 index 0000000..9cef76d --- /dev/null +++ b/assets/css/main.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/skins/highlight/tomorrow/_highlight.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/skins/highlight/tomorrow/_default.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/_classes.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/classes/_clearfix.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/classes/_media.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/classes/_display.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/classes/_horizontal-rules.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/skins/_default.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/classes/_text.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/classes/_transition.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/classes/_clickable.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/classes/_pseudo.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/classes/_link.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/classes/_overflow.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/classes/_shadow.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/classes/_spacing.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/classes/_grid.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/classes/_flex.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/_reset.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/_print.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/components/_button.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/classes/_user-select.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/components/_image.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/components/_card.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/components/_gallery.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/components/_hero.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/components/_menu.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/components/_modal.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/classes/_transform.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/components/_toc.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/classes/_split-line.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/components/_item.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/components/_swiper.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/classes/_animation.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/animate/_fade-in.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/animate/_fade-in-down.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/animate/_fade-in-up.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/components/_main.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/components/_header.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/components/_footer.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/components/_article-list.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/components/_article-info.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/components/_article-header.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/components/_article-content.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/components/_article-footer.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/components/_author-links.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/components/_author-profile.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/components/_tags.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/components/_search.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/common/_variables.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/components/_lightbox.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/components/_extensions.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/additional/_alert.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/additional/_tag.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/additional/_photo-frame.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/layout/_base.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/layout/_page.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/layout/_article.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/layout/_articles.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/layout/_archive.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/layout/_home.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/layout/_landing.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/layout/_404.scss","../../../usr/local/bundle/gems/jekyll-text-theme-2.2.6/_sass/custom.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;;AAEF;EACE;;;AAEF;EACE,OCPwB;EDQxB;AACoC;AACJ;AACG;AACA;AACI;AACN;AACM;AACH;AACA;AACA;AACA;AACJ;AACH;AAC6B;AACxB;AACqB;AAC3B;AACwB;AACjB;AACA;AACF;AACE;AACA;AACA;AACD;AACC;AACD;AACD;AACM;AACJ;AACH;AACC;AACM;AACP;AACC;AACM;AACJ;AACF;AACM;AACN;AACD;AACC;AACM;AACJ;AACA;AACA;AACA;AACD;AACK;AACH;AACF;AACC;AACD;AACC;AACD;AACA;AACA;AACA;AACK;AACP;AACA;AACA;AACG;;AA9DnC;EAAO,OCRiB;;ADSxB;EAAO,OCRiB;;ADSxB;EAAO,OCHiB;;ADIxB;EAAO,OCTiB;;ADUxB;EAAO,OCbiB;;ADcxB;EAAO,OCRiB;;ADSxB;EAAO,OCfiB;;ADgBxB;EAAO,OCfiB;;ADgBxB;EAAO,OChBiB;;ADiBxB;EAAO,OCjBiB;;ADkBxB;EAAO,OClBiB;;ADmBxB;EAAO,OClBiB;;ADmBxB;EAAO;;AACP;EAAO;EAAmB,OCtBF;;ADuBxB;EAAO,OClBiB;;ADmBxB;EAAO;EAAmB,OCvBF;;ADwBxB;EAAO;;AACP;EAAO;EAAmB,OCpBF;;ADqBxB;EAAO,OCnBiB;;ADoBxB;EAAO,OCpBiB;;ADqBxB;EAAO,OCvBiB;;ADwBxB;EAAO,OCtBiB;;ADuBxB;EAAO,OCvBiB;;ADwBxB;EAAO,OC5BiB;;AD6BxB;EAAO,OC5BiB;;AD6BxB;EAAO,OC/BiB;;ADgCxB;EAAO,OC9BiB;;AD+BxB;EAAO,OC7BiB;;AD8BxB;EAAO,OCrCiB;;ADsCxB;EAAO,OClCiB;;ADmCxB;EAAO,OCrCiB;;ADsCxB;EAAO,OClCiB;;ADmCxB;EAAO,OCzCiB;;AD0CxB;EAAO,OCxCiB;;ADyCxB;EAAO,OCpCiB;;ADqCxB;EAAO,OC5CiB;;AD6CxB;EAAO,OCzCiB;;AD0CxB;EAAO,OCvCiB;;ADwCxB;EAAO,OC/CiB;;ADgDxB;EAAO,OC1CiB;;AD2CxB;EAAO,OC/CiB;;ADgDxB;EAAO,OC5CiB;;AD6CxB;EAAO,OCnDiB;;ADoDxB;EAAO,OCjDiB;;ADkDxB;EAAO,OClDiB;;ADmDxB;EAAO,OCnDiB;;ADoDxB;EAAO,OCpDiB;;ADqDxB;EAAO,OCnDiB;;ADoDxB;EAAO,OCzDiB;;AD0DxB;EAAO,OCzDiB;;AD0DxB;EAAO,OCtDiB;;ADuDxB;EAAO,OCzDiB;;AD0DxB;EAAO,OCxDiB;;ADyDxB;EAAO,OC3DiB;;AD4DxB;EAAO,OC1DiB;;AD2DxB;EAAO,OC3DiB;;AD4DxB;EAAO,OC5DiB;;AD6DxB;EAAO,OC7DiB;;AD8DxB;EAAO,OCnEiB;;ADoExB;EAAO,OClEiB;;ADmExB;EAAO,OCnEiB;;ADoExB;EAAO,OCpEiB;;ADqExB;EAAO,OCpEiB;;;ACJ1B;ACCE;EACE;EACA;EACA;;;AAQJ;EACE;;;AAGF;EACE;;;ACJA;ECXE;IACE;;;ADUJ;ECXE;IACE;;;ADUJ;ECXE;IACE;;;AAMJ;EADF;IAEI;;;;ACTF;EACE;EACA;EACA,OCqBuB;EDpBvB;EACA;EACA;;;AEwBJ;EA9BE,ODuByB;;ACtBzB;EACE,ODoBuB;;AClBzB;EACE,ODkBuB;;AChBzB;EACE,ODgBuB;;ACdzB;ECVA,oBCuBoB;EDtBpB,YCsBoB;;ACvBpB;ECCE;;ADOF;ECJE;;ADUF;ECPE;;ADPF;ED4FE,OHrEuB;;AIfzB;ED+FE,OHjGe;;AIQjB;EDoGE,OAvDiB;;AChCnB;ED+GI;;;AFzGN;EAlBE,ODYyB;;ACXzB;EACE,ODSuB;;ACPzB;EACE,ODOuB;;ACLzB;EACE,ODKuB;;ACHzB;ECzBA,oBCuBoB;EDtBpB,YCsBoB;;ACvBpB;ECCE;;ADOF;ECJE;;ADUF;ECPE;;ADPF;ED4FE,OHjEuB;;AInBzB;ED+FE,OHjGe;;AIQjB;EDoGE,OAvDiB;;AChCnB;ED+GI;;;AGxHN;EAjBI,UAkBgB;EALhB;;;AAQJ;EArBI,UAsBgB;;;ACTpB;EAPI;;;AAWJ;EARI;;;ACuDI;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;EAAA;;;AAeF;EAfE;EAAA;;;AAeF;EAfE;EAAA;;;AAeF;EAfE;EAAA;;;AAeF;EAfE;EAAA;;;AAeF;EAfE;EAAA;;;AAeF;EAfE;EAAA;;;AAeF;EAfE;EAAA;;;AAeF;EAfE;EAAA;;;AAeF;EAfE;EAAA;;;AAeF;EAfE;EAAA;;;AAeF;EAfE;EAAA;;;AAeF;EArBE;;;AAqBF;EArBE;;;AAqBF;EArBE;;;AAqBF;EArBE;;;AAqBF;EArBE;;;AAqBF;EArBE;;;AAqBF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;;;AAeF;EAfE;EAAA;;;AAeF;EAfE;EAAA;;;AAeF;EAfE;EAAA;;;AAeF;EAfE;EAAA;;;AAeF;EAfE;EAAA;;;AAeF;EAfE;EAAA;;;AAeF;EAfE;EAAA;;;AAeF;EAfE;EAAA;;;AAeF;EAfE;EAAA;;;AAeF;EAfE;EAAA;;;AAeF;EAfE;EAAA;;;AAeF;EAfE;EAAA;;;AAeF;EArBE;;;AAqBF;EArBE;;;AAqBF;EArBE;;;AAqBF;EArBE;;;AAqBF;EArBE;;;AAqBF;EArBE;;;AA6BN;EApCI;;;AAoCJ;EApCI;;;AAoCJ;EApCI;;;AAoCJ;EApCI;;;AAoCJ;EApCI;EAAA;;;AAoCJ;EApCI;EAAA;;;AAoCJ;EAtCI;;;AClCR;EHAI,UGCgB;;;AAEpB;EACE;;;AA0BF;EC8BE;EACA;EACA;EACA;EACA;EAmEA,mBDnGmB;ECoGnB,gBDpGmB;ECwGjB,eDxGiB;EC0GnB,WD1GmB;;AZrBnB;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBAHe;IAIf;IACA,eALe;IAMf;IACA;IACA;IDjQE;;;AZCF;EYaE;IC8OF,kBAHe;IAIf;IACA,eALe;IAMf;IACA;IACA;ID9PE;;;AZFF;EYaE;IC8OF,kBDvPgB;ICwPhB;IACA,eDzPgB;IC0PhB;IACA;IACA;;;AbhQA;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBAHe;IAIf;IACA,eALe;IAMf;IACA;IACA;IDjQE;;;AZCF;EYaE;IC8OF,kBAHe;IAIf;IACA,eALe;IAMf;IACA;IACA;ID9PE;;;AZFF;EYaE;IC8OF,kBDvPgB;ICwPhB;IACA,eDzPgB;IC0PhB;IACA;IACA;;;AbhQA;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBDrPgB;ICsPhB;IACA,eDvPgB;ICwPhB;IACA;IACA;IDzPE;;;AZPF;EYaE;IC8OF,kBAHe;IAIf;IACA,eALe;IAMf;IACA;IACA;IDjQE;;;AZCF;EYaE;IC8OF,kBAHe;IAIf;IACA,eALe;IAMf;IACA;IACA;ID9PE;;;AZFF;EYaE;IC8OF,kBDvPgB;ICwPhB;IACA,eDzPgB;IC0PhB;IACA;IACA;;;;AD9NF;EACE;;;AAmBQ;EDlBA;EAAA;;ACoBE;EDlBF;EAAA;;;ACgBA;EDlBA;EAAA;;ACoBE;EDlBF;EAAA;;;ACgBA;EDlBA;EAAA;;ACoBE;EDlBF;EAAA;;;ACgBA;EDlBA;EAAA;;ACoBE;EDlBF;EAAA;;;ACgBA;EDlBA;EAAA;;ACoBE;EDlBF;EAAA;;;ACgBA;EDlBA;EAAA;;ACoBE;EDlBF;EAAA;;;ACgBA;EDlBA;EAAA;;ACoBE;EDlBF;EAAA;;;ACgBA;EDlBA;EAAA;;ACoBE;EDlBF;EAAA;;;ACgBA;EDlBA;EAAA;;ACoBE;EDlBF;EAAA;;;ACgBA;EDlBA;EAAA;;ACoBE;EDlBF;EAAA;;;ACgBA;EDlBA;EAAA;;ACoBE;EDlBF;EAAA;;;ACgBA;EDlBA;EAAA;;ACoBE;EDlBF;EAAA;;;ACSA;EDjBA;;ACmBE;EDjBF;;;ACeA;EDjBA;;ACmBE;EDjBF;;;ACeA;EDjBA;;ACmBE;EDjBF;;;ACeA;EDjBA;;ACmBE;EDjBF;;;ACeA;EDjBA;;ACmBE;EDjBF;;;ACeA;EDjBA;;ACmBE;EDjBF;;;AbtBV;AgBIA;AAAA;AAAA;EAGE;EACA;;;AAGF;AAAA;AAAA;AAIA;EACE;EACA;;AACA;EAHF;IAII;;;;AAIJ;EACE;EACA;EACA;;AACA;EACE,YXFe;;AWIjB;EACE,YXLe;;AWOjB;EACE,YXRe;;;AWhDjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EA6DA;EACA;;;AAGF;EACE;EACA,OX5CyB;;;AW+C3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAOE;;;AAGF;EACE;EACA,OX5DyB;;AHnBzB;Ec6EF;IAII;;;;AAIJ;EACE;EACA,OXpEyB;;AHnBzB;EcqFF;IAII;;;;AAIJ;EACE;EACA,OX5EyB;;AHnBzB;Ec6FF;IAII;;;;AAIJ;EACE;EACA,OXnFyB;;AHpBzB;EcqGF;IAII;;;;AAIJ;EACE;EACA,OX3FyB;;AHpBzB;Ec6GF;IAII;;;;AAIJ;EACE;EACA,OXlGyB;;AHrBzB;EcqHF;IAII;;;;AAIJ;EACE;ETjIA,oBCuBoB;EDtBpB,YCsBoB;;ACvBpB;ECCE;;ADOF;ECJE;;ADUF;ECPE;;ADPF;ED4FE,OHtFe;;AIEjB;ED+FE,OAnEgB;;ACtBlB;EDoGE,OAvDiB;;AChCnB;ED+GI;;;AQLN;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAIA;EACE;;AAEF;EACE;;;AAKJ;EACE;;;AC5KF;ERCE;IQEI;;ERMJ;IQHI;;ERSJ;IQNI;;EAIJ;AAAA;AAAA;AAAA;IAIE;;;ACAJ;EAhBE;EACA;EACA;EACA;EACA;EACA;ECLA,qBDMqB;ECLrB,kBDKqB;ECJrB,iBDIqB;ECHrB,aDGqB;;AACrB;EACE;EACA;;ATkBF;ESfE;;;AAQJ;EXpBE,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrFe;EGuFb,kBHxFa;;AG2Fb;EACE,MH3FW;;AICjB;ED+FE,OHhGe;EGkGb,kBAzDe;;AA4Df;EACE,MHtGW;;AIOjB;EDoGE,OH3Ge;EG6Gb,kBA7CgB;;AAgDhB;EACE,MHjHW;;AIcjB;EDwGE,OA9HoI;EAgIlI,kBA/Ee;EAgFf;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;;AU7HV;EXxBE,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OH/Ee;EGiFb,kBHlFa;;AGqFb;EACE,MHrFW;;AILjB;ED+FE,OH1Fe;EG4Fb,kBAzDe;;AA4Df;EACE,MHhGW;;AICjB;EDoGE,OHrGe;EGuGb,kBA7CgB;;AAgDhB;EACE,MH3GW;;AIQjB;EDwGE,OA9HoI;EAgIlI,kBA/Ee;EAgFf;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;;AUzHV;EX5BE,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrCkB;EGuChB,kBH3CY;;AG8CZ;EACE,MH3Cc;;AI/CpB;ED+FE,OHhDkB;EGkDhB,kBAzDe;;AA4Df;EACE,MHtDc;;AIzCpB;EDoGE,OH3DkB;EG6DhB,kBA7CgB;;AAgDhB;EACE,MHjEc;;AIlCpB;EDwGE,OA9HoI;EAgIlI,kBA/Ee;EAgFf;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;;AUrHV;EXhCE,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrCkB;EGuChB,kBH1CY;;AG6CZ;EACE,MH3Cc;;AI/CpB;ED+FE,OHhDkB;EGkDhB,kBAzDe;;AA4Df;EACE,MHtDc;;AIzCpB;EDoGE,OH3DkB;EG6DhB,kBA7CgB;;AAgDhB;EACE,MHjEc;;AIlCpB;EDwGE,OA9HoI;EAgIlI,kBA/Ee;EAgFf;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;;AUjHV;EXpCE,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrCkB;EGuChB,kBHzCY;;AG4CZ;EACE,MH3Cc;;AI/CpB;ED+FE,OHhDkB;EGkDhB,kBAzDe;;AA4Df;EACE,MHtDc;;AIzCpB;EDoGE,OH3DkB;EG6DhB,kBA7CgB;;AAgDhB;EACE,MHjEc;;AIlCpB;EDwGE,OA9HoI;EAgIlI,kBA/Ee;EAgFf;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;;AU7GV;EXxCE,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrCkB;EGuChB,kBHxCY;;AG2CZ;EACE,MH3Cc;;AI/CpB;ED+FE,OHhDkB;EGkDhB,kBAzDe;;AA4Df;EACE,MHtDc;;AIzCpB;EDoGE,OH3DkB;EG6DhB,kBA7CgB;;AAgDhB;EACE,MHjEc;;AIlCpB;EDwGE,OA9HoI;EAgIlI,kBA/Ee;EAgFf;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;;AUzGV;EX5CE,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHjEuB;EGmErB,kBH/EmB;;AGkFnB;EACE,MHvEmB;;AInBzB;ED+FE,OH5EuB;EG8ErB,kBAtDe;;AAyDf;EACE,MHlFmB;;AIbzB;EDoGE,OHvFuB;EGyFrB,kBA1CgB;;AA6ChB;EACE,MH7FmB;;AINzB;EDwGE,OA9HoI;EAgIlI,kBA5Ee;EA6Ef;;AAGA;EACE,MArIgI;;AC4BtI;EDkHI;EAGA;;AAGA;EAKI;;;AUxGV;EXhDE,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrEuB;EGuErB,kBH9EmB;;AGiFnB;EACE,MH3EmB;;AIfzB;ED+FE,OHhFuB;EGkFrB,kBAzDe;;AA4Df;EACE,MHtFmB;;AITzB;EDoGE,OH3FuB;EG6FrB,kBA7CgB;;AAgDhB;EACE,MHjGmB;;AIFzB;EDwGE,OA9HoI;EAgIlI,kBA/Ee;EAgFf;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;;AUjGV;EACE,Ob/CiB;EagDjB;EXtDA,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHtFe;EGwFb,kBUvC8B;;AV0C9B;EACE,MH5FW;;AIEjB;ED+FE,OHhGe;EGkGb,kBHnGa;;AGsGb;EACE,MHtGW;;AIOjB;EDoGE,OH3Ge;EG6Gb,kBA7CgB;;AAgDhB;EACE,MHjHW;;AIcjB;EDwGE,OA9HoI;EAgIlI,kBHzHa;EG0Hb;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;;AU3FV;EACE,Ob/CiB;EagDjB;EX5DA,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHhFe;EGkFb,kBUjC8B;;AVoC9B;EACE,MHtFW;;AIJjB;ED+FE,OH1Fe;EG4Fb,kBH7Fa;;AGgGb;EACE,MHhGW;;AICjB;EDoGE,OHrGe;EGuGb,kBA7CgB;;AAgDhB;EACE,MH3GW;;AIQjB;EDwGE,OA9HoI;EAgIlI,kBHnHa;EGoHb;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;;AUrFV;EACE,ObdgB;EaehB;EXlEA,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHzCc;EG2CZ,kBU3BuB;;AV8BvB;EACE,MH/CU;;AI3ChB;ED+FE,OHhDkB;EGkDhB,kBHtDY;;AGyDZ;EACE,MHtDc;;AIzCpB;EDoGE,OH3DkB;EG6DhB,kBA7CgB;;AAgDhB;EACE,MHjEc;;AIlCpB;EDwGE,OA9HoI;EAgIlI,kBH5EY;EG6EZ;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;;AU/EV;EACE,ObnBgB;EaoBhB;EXxEA,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHxCc;EG0CZ,kBUrBsB;;AVwBtB;EACE,MH9CU;;AI5ChB;ED+FE,OHhDkB;EGkDhB,kBHrDY;;AGwDZ;EACE,MHtDc;;AIzCpB;EDoGE,OH3DkB;EG6DhB,kBA7CgB;;AAgDhB;EACE,MHjEc;;AIlCpB;EDwGE,OA9HoI;EAgIlI,kBH3EY;EG4EZ;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;;AUzEV;EACE,ObxBgB;EayBhB;EX9EA,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHvCc;EGyCZ,kBUfwB;;AVkBxB;EACE,MH7CU;;AI7ChB;ED+FE,OHhDkB;EGkDhB,kBHpDY;;AGuDZ;EACE,MHtDc;;AIzCpB;EDoGE,OH3DkB;EG6DhB,kBA7CgB;;AAgDhB;EACE,MHjEc;;AIlCpB;EDwGE,OA9HoI;EAgIlI,kBH1EY;EG2EZ;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;;AUnEV;EACE,Ob7BgB;Ea8BhB;EXpFA,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHtCc;EGwCZ,kBUTqB;;AVYrB;EACE,MH5CU;;AI9ChB;ED+FE,OHhDkB;EGkDhB,kBHnDY;;AGsDZ;EACE,MHtDc;;AIzCpB;EDoGE,OH3DkB;EG6DhB,kBA7CgB;;AAgDhB;EACE,MHjEc;;AIlCpB;EDwGE,OA9HoI;EAgIlI,kBHzEY;EG0EZ;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;;AU7DV;EACE,Ob1EuB;Ea2EvB;EX1FA,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OH7EqB;EG+EnB,kBUHwC;;AVMxC;EACE,MHnFiB;;AIPvB;ED+FE,OH5EuB;EG8ErB,kBH1FmB;;AG6FnB;EACE,MHlFmB;;AIbzB;EDoGE,OHvFuB;EGyFrB,kBA1CgB;;AA6ChB;EACE,MH7FmB;;AINzB;EDwGE,OA9HoI;EAgIlI,kBHhHmB;EGiHnB;;AAGA;EACE,MArIgI;;AC4BtI;EDkHI;EAGA;;AAGA;EAKI;;;AU1DV;EACE,Ob/EuB;EagFvB;EXhGA,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OH5EqB;EG8EnB,kBUGuC;;AVAvC;EACE,MHlFiB;;AIRvB;ED+FE,OHhFuB;EGkFrB,kBHzFmB;;AG4FnB;EACE,MHtFmB;;AITzB;EDoGE,OH3FuB;EG6FrB,kBA7CgB;;AAgDhB;EACE,MHjGmB;;AIFzB;EDwGE,OA9HoI;EAgIlI,kBH/GmB;EGgHnB;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;;AUjDV;EACE;;;AAIF;EACE;;;AAIF;EHrCE;EACA;EACA;EACA;EACA;EAiOE,kBG9LuB;EH+LvB,eG/LuB;EHiMzB,yBGjMyB;EHkMzB,sBGlMyB;EHmMzB,iBGnMyB;EHiOvB,mBGhOmB;EHiOnB,gBGjOmB;EHmOrB,qBGnOqB;EHoOrB,kBGpOqB;EHqOrB,aGrOqB;EACrB;;;AAIF;EACE;EACA;;AACA;EACE;EACA;;;AAIJ;EACE;EACA;;AACA;EACE;EACA;;;AAIJ;EACE;EACA;;AACA;EACE;EACA;;;AAIJ;EACE;EACA;;AACA;EACE;EACA;;;AAIJ;EACE;EACA;;AACA;EACE;EACA;;;AEhKJ;EACE;;;AAGF;EACE;;;AAEF;EACE;;;AAEF;EACE;;;AAEF;EACE;;;AAEF;EACE;;;AChBF;EACE;EACA;ETKE;ELPF,oBcIoB;EdHpB,YcGoB;;AACpB;EACE;EACA;;AAEF;EACE;EACA;;;AAKF;EACE;;;AAIJ;EACE;;;AAGF;EdzBE,oBCuBoB;EDtBpB,YCsBoB;;ACvBpB;ECCE;;ADOF;ECJE;;ADUF;ECPE;;ADPF;ED4FE,OHtEuB;;AIdzB;ED+FE,OHjGe;;AIQjB;EDoGE,OAvDiB;;AChCnB;ED+GI;;;Aa7GN;EACE;EACA;;AACA;EACE;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;;AACA;EACE;;AAGJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEF;EACE;EACA;;AAEF;EAEE;;AAEF;EAEE;;;AAIJ;EACE;Ed9EA,oBcuFoB;EdtFpB,YcsFoB;;AZ/EpB;EGEE;;ASwEE;EACE;;;AAOR;ETtFI;;ASyFA;EACE;;AAGJ;EACE;EACA;;;ACpGJ;EACE;EP6DA;EACA;EACA;EACA;EACA;EAmCE;EACA;EAQF,wBO3GwB;EP4GxB,qBO5GwB;EP6GxB,oBO7GwB;EP8GxB,gBO9GwB;;;AAG1B;EPkQE,kBOjQc;EPkQd;EACA,eOnQc;EPoQd;EACA;EACA;;;AOnQF;EPoDE;EACA;EACA;EACA;EACA;EAgRE,mBOtUmB;EPuUnB,gBOvUmB;EPyUrB,qBOzUqB;EP0UrB,kBO1UqB;EP2UrB,aO3UqB;EPmSnB,kBOlSuB;EPmSvB,eOnSuB;EPqSzB,yBOrSyB;EPsSzB,sBOtSyB;EPuSzB,iBOvSyB;EACzB;EACA;;;AAGF;EACE;;;ACnBF;EACE;ER6DA;EACA;EACA;EACA;EACA;EAmCE;EACA;EAQF,wBQ3GwB;ER4GxB,qBQ5GwB;ER6GxB,oBQ7GwB;ER8GxB,gBQ9GwB;ER4StB,kBQ3SuB;ER4SvB,eQ5SuB;ER8SzB,yBQ9SyB;ER+SzB,sBQ/SyB;ERgTzB,iBQhTyB;EA0BzB;;AAzBA;EAAK;;AACL;EAAK;;AACL;EAAK;;AACL;EAAK;;AACL;EAAK;;AACL;EAAK;;AACL;EAAK;;ArBPL;EqBSE;IAAK;;EACL;IAAK;;EACL;IAAK;;EACL;IAAK;;EACL;IAAK;;EACL;IAAK;;EACL;IAAK;;;ArBfP;EqBkBE;IAAK;;EACL;IAAK;;EACL;IAAK;;EACL;IAAK;;EACL;IAAK;;EACL;IAAK;;EACL;IAAK;;;;AAMT;EACE;;AAcF;EACE;;ArB9CA;EqB6CF;IAGI;;;ArBhDF;EqB6CF;IAMI;;;;AAKF;EACE;;ArBzDF;EqBwDA;IAGI;;;ArB3DJ;EqBwDA;IAMI;;;;AC5BN;ETwBE;EACA;EACA;EACA;EACA;EAmEA,mBSlHS;ETmHT,gBSnHS;ETuHP,eSvHO;ETyHT,WSzHS;EAIT;EACA;ETmFE;EACA;EAEF,wBSvG0B;ETwG1B,qBSxG0B;ETyG1B,oBSzG0B;ET0G1B,gBS1G0B;ET2UxB,mBSzSmB;ET0SnB,gBS1SmB;ET4SrB,qBS5SqB;ET6SrB,kBS7SqB;ET8SrB,aS9SqB;;AAhBrB;EAEI;EACA;EAEF;EACA;;AACA;EACE;;;AAWN;ETyDI;EACA;EAQF,wBSzG0B;ET0G1B,qBS1G0B;ET2G1B,oBS3G0B;ET4G1B,gBS5G0B;ET6UxB,mBSpSmB;ETqSnB,gBSrSmB;ETuSrB,qBSvSqB;ETwSrB,kBSxSqB;ETySrB,aSzSqB;;AACrB;EACE;;;AAIJ;ETsBE;EACA;EACA;EACA;EACA;;;AStBF;ETuPI,kBStPuB;ETuPvB,eSvPuB;ETyPzB,yBSzPyB;ET0PzB,sBS1PyB;ET2PzB,iBS3PyB;;;AAG3B;ETyEE,mBSxEmB;ETyEnB,gBSzEmB;ET2EjB;EAIF,WS/EmB;;;AAGrB;ETmIE,kBSlImB;ETmInB,mBSnImB;EToInB,gBSpImB;ETqInB,mBSrImB;ETsInB,WStImB;;;ACnCrB;EApBE;EACA;EACA;EACA,SAXY;EAYZ;EACA;EACA,OpBYyB;EoBXzB;EACA,kBpB8BiB;EoB7BjB;EClBA,mBDmBmB;EClBnB,WDkBmB;ElBnBnB,oBkBoBoB;ElBnBpB,YkBmBoB;;;AAYtB;EARE;ECxBA,mBDyBmB;ECxBnB,WDwBmB;ElBzBnB,oBkB0BoB;ElBzBpB,YkByBoB;;;AAStB;EdlCI,UcmCgB;EdtBhB;;;AgBfJ;EACE;EACA;EACA,OtBqByB;EsBpBzB;;AACA;EACE;;AACA;EACE;EACA;EACA;;AASF;AAAA;AAAA;AAAA;AAAA;AAAA;EpBlBF,oBCuBoB;EDtBpB,YCsBoB;;ACvBpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ECCE;;ADOF;AAAA;AAAA;AAAA;AAAA;AAAA;ECJE;;ADUF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ECPE;;ADPF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ED4FE,OHrEuB;;AIfzB;AAAA;AAAA;AAAA;AAAA;AAAA;ED+FE,OHjGe;;AIQjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EDoGE,OAvDiB;;AChCnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ED+GI;;AmBpHA;AAAA;AAAA;AAAA;AAAA;AAAA;EpBtBJ,oBCuBoB;EDtBpB,YCsBoB;;ACvBpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ECCE;;ADOF;AAAA;AAAA;AAAA;AAAA;AAAA;ECJE;;ADUF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ECPE;;ADPF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ED4FE,OHtFe;;AIEjB;AAAA;AAAA;AAAA;AAAA;AAAA;ED+FE,OAnEgB;;ACtBlB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EDoGE,OAvDiB;;AChCnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ED+GI;;AmBzGF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EACE;EACA;EACA;;AAGJ;EC9BA;EAQE,qBAVQ;EDkCR;EACA;EACA,OtBrBuB;;AsBsBvB;EACE;EACA;EACA;;AAEF;EpBjDF,oBCuBoB;EDtBpB,YCsBoB;;ACvBpB;ECCE;;ADOF;ECJE;;ADUF;ECPE;;ADPF;ED4FE,OHtEuB;;AIdzB;ED+FE,OHjGe;;AIQjB;EDoGE,OAvDiB;;AChCnB;ED+GI;;AmBpFF;EACE;;AAGJ;EACE;;AAEF;EACE;;AAEF;AAAA;EAEE;;AAEF;EACE,OtB7CuB;;AsB8CvB;EpBtEF,oBCuBoB;EDtBpB,YCsBoB;;ACvBpB;ECCE;;ADOF;ECJE;;ADUF;ECPE;;ADPF;ED4FE,OHpEuB;;AIhBzB;ED+FE,OHjGe;;AIQjB;EDoGE,OAvDiB;;AChCnB;ED+GI;;;AmB7DJ;EhB5EE,UgB6EkB;EAClB;EACA;;;AAMA;EACE;EACA;;AAGA;EACE;ECnFN;EAWE,mBDyE8B;;AAIhC;AAAA;AAAA;EAGE,OtB5EuB;;AsB6EvB;AAAA;AAAA;EpBrGF,oBCuBoB;EDtBpB,YCsBoB;;ACvBpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ECCE;;ADOF;AAAA;AAAA;ECJE;;ADUF;AAAA;AAAA;AAAA;AAAA;ECPE;;ADPF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ED4FE,OHpEuB;;AIhBzB;AAAA;AAAA;ED+FE,OAnEgB;;ACtBlB;AAAA;AAAA;AAAA;AAAA;EDoGE,OAvDiB;;AChCnB;AAAA;AAAA;AAAA;AAAA;ED+GI;;AmBjCJ;EAKE,OtBvFuB;;AsBmFvB;EACE;EACA;;AAGF;EpB/GF,oBCuBoB;EDtBpB,YCsBoB;;ACvBpB;ECCE;;ADOF;ECJE;;ADUF;ECPE;;ADPF;ED4FE,OHtEuB;;AIdzB;ED+FE,OAhEgB;;ACzBlB;EDoGE,OApDiB;;ACnCnB;EDkHI;;AmBzBF;EACE;EACA;EACA;;;AExHN;Ed8DE;EACA;EACA;EACA;EACA;;Ab9DA;E2BJF;IdqGI;IACA;IAQF,wBc3G0B;Id4G1B,qBc5G0B;Id6G1B,oBc7G0B;Id8G1B,gBc9G0B;;;;AAI5B;EACE;;AAEE;EACE;;AACA;EACE;;A3BTN;E2BGF;IAWI;;;;AAIJ;EdkPE,kBcjPc;EdkPd;EACA,ecnPc;EdoPd;EACA;EACA;EcrPA;;;AAGF;EtB1BE,oBCuBoB;EDtBpB,YCsBoB;;ACvBpB;ECCE;;ADOF;ECJE;;ADUF;ECPE;;ADPF;ED4FE,OHtEuB;;AIdzB;ED+FE,OHjGe;;AIQjB;EDoGE,OAvDiB;;AChCnB;ED+GI;;;AqB5GN;EACE,OxBPyB;;;AwBWzB;EACE;EACA;;AbrCF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EauCI;EACA;;AbnBJ;EasBI;;AAEF;EACE,OxBvBqB;;AwByBvB;EACE;EACA;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;A3BpEJ;E2BmEE;IAGI;;;;AAQJ;EACE;;;AAMJ;EAQE;;AAPA;EACE;;AAEF;EACE;EDpFJ;EAQE,qBAVQ;;;AERZ;EACE;EnBCE;;;AmBGJ;EACE;EACA;;;AAGF;EfoDE;EACA;EACA;EACA;EACA;;;AepDF;EvBbE,oBuBcoB;EvBbpB,YuBaoB;;;AAGtB;EfoME,qBenMqB;EfoMrB,kBepMqB;EfqMrB,mBerMqB;EfsMrB,aetMqB;;AACrB;EACE;;;AAIJ;EACE;EACA;EJ1BA,mBI4BmB;EJ3BnB,WI2BmB;EvB5BnB,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHtEuB;EGwErB,kBsBjE8B;;AtBoE9B;EACE,MH5EmB;;AIdzB;ED+FE,OHjFuB;EGmFrB,kBAzDe;;AA4Df;EACE,MHvFmB;;AIRzB;EDoGE,OH5FuB;EG8FrB,kBA7CgB;;AAgDhB;EACE,MHlGmB;;AIDzB;EDwGE,OA9HoI;EAgIlI,kBA/Ee;EAgFf;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;;AsBrHV;EvBhCE,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrEuB;EGuErB,kBsB7DwC;;AtBgExC;EACE,MH3EmB;;AIfzB;ED+FE,OHhFuB;EGkFrB,kBAzDe;;AA4Df;EACE,MHtFmB;;AITzB;EDoGE,OH3FuB;EG6FrB,kBA7CgB;;AAgDhB;EACE,MHjGmB;;AIFzB;EDwGE,OA9HoI;EAgIlI,kBA/Ee;EAgFf;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;;AsBjHV;EvBpCE,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHjEuB;EGmErB,kBsBzDuC;;AtB4DvC;EACE,MHvEmB;;AInBzB;ED+FE,OH5EuB;EG8ErB,kBAtDe;;AAyDf;EACE,MHlFmB;;AIbzB;EDoGE,OHvFuB;EGyFrB,kBA1CgB;;AA6ChB;EACE,MH7FmB;;AINzB;EDwGE,OA9HoI;EAgIlI,kBA5Ee;EA6Ef;;AAGA;EACE,MArIgI;;AC4BtI;EDkHI;EAGA;;AAGA;EAKI;;;AsBhHV;EACE;;;AAGF;EACE;;;ACxCA;ECLA;IACE;;EAEF;IACE;;;ADIF;ECRA;IACE;;EAEF;IACE;;;ADCF;EELA;IACE;IPDF,mBOEqB;IPDrB,WOCqB;;EAErB;IACE;IPLF,mBOMqB;IPLrB,WOKqB;;;AFErB;EERA;IACE;IPDF,mBOEqB;IPDrB,WOCqB;;EAErB;IACE;IPLF,mBOMqB;IPLrB,WOKqB;;;AFDrB;EGLA;IACE;IRDF,mBQEqB;IRDrB,WQCqB;;EAErB;IACE;IRLF,mBQMqB;IRLrB,WQKqB;;;AHErB;EGRA;IACE;IRDF,mBQEqB;IRDrB,WQCqB;;EAErB;IACE;IRLF,mBQMqB;IRLrB,WQKqB;;;ACPvB;EACE;EACA;EACA;EACA;;AjCAA;EiCJF;IAMI;;;AjCFF;EiCJF;IASI;;;;AAKF;EACE;;AjCXF;EiCUA;IAGI;;;;AAMJ;EACE;EACA;;;ACzBJ;EACE,Y/BYiB;;A+BXjB;EACE;EACA;E7BHF,oBCuBoB;EDtBpB,YCsBoB;;ACvBpB;ECCE;;ADOF;ECJE;;ADUF;ECPE;;ADPF;ED4FE,OH/Ee;;AILjB;ED+FE,OHjGe;;AIQjB;EDoGE,OAvDiB;;AChCnB;ED+GI;;A4BpIJ;ErBuDA;EACA;EACA;EACA;EACA;;Ab9DA;EkCGA;IrB8FE;IACA;IAQF,wBqBpG4B;IrBqG5B,qBqBrG4B;IrBsG5B,oBqBtG4B;IrBuG5B,gBqBvG4B;;;;AAK9B;EAEE;;AAEE;ERTF;EAQE,qBQE8B;;;AAKlC;EAEE;;AAEE;ERnBF;EAQE,qBQY8B;;;AAKlC;ErB2BE;EACA;EACA;EACA;EACA;EAmEA,mBSlHS;ETmHT,gBSnHS;ETuHP,eSvHO;ETyHT,WSzHS;EAIT;EACA;ET0TE,mBqB7SmB;ErB8SnB,gBqB9SmB;ErBgTrB,qBqBhTqB;ErBiTrB,kBqBjTqB;ErBkTrB,aqBlTqB;ErBgGrB,mBqB/FmB;ErBgGnB,gBqBhGmB;ErBkGjB;EAIF,WqBtGmB;ErBkOnB,kBqBjOc;ErBkOd;EACA,eqBnOc;ErBoOd;EACA;EACA;EqBrOA;EACA;EACA;;AZjBA;EAEI;EACA;EAEF;EACA;;AACA;EACE;;AtB7BJ;EkC+BF;IASI;IACA;;;AAEF;ErByNA,kBqBxNgB;ErByNhB;EACA,eqB1NgB;ErB2NhB;EACA;EACA;;AbzQA;EkC2CA;IAGI;;;AAGJ;EACE;EACA;;AlCnDF;EkCiDA;IrBSA;IACA;IACA;IACA;IACA;;;;AqBJF;ErBAE;EACA;EACA;EACA;EACA;EAgRE,mBqBlRmB;ErBmRnB,gBqBnRmB;ErBqRrB,qBqBrRqB;ErBsRrB,kBqBtRqB;ErBuRrB,aqBvRqB;;AACrB;EACE;EACA;EACA;EACA;;AlCjEF;EkC6DA;IAMI;IACA;;;AAGJ;EACE;EACA;;AAOJ;EzB/EM;EAEF,YyB8EgB;EzBtEhB;;AyBuEF;EACE;EACA;EACA;ErB3BF;EACA;EACA;EACA;EACA;EAmEA,mBqBvCsB;ErBwCtB,gBqBxCsB;ErB0CpB;EAIF,WqB9CsB;EZvEtB;EACA;ET0TE,mBqBnPqB;ErBoPrB,gBqBpPqB;ErBsPvB,qBqBtPuB;ErBuPvB,kBqBvPuB;ErBwPvB,aqBxPuB;;AlC3FvB;EkCkFA;IAKI;IACA;;;AZnEJ;EAEI;EACA;EAEF;EACA;;AACA;EACE;;AtB7BJ;EkCkFA;IAWI;;;AlC7FJ;EkC+FE;IAEI;;;;AAON;EACE;EACA;EACA;ERrGF;EAQE,qBQ8F4B;;;AAI9B;E7BnHA,oBCuBoB;EDtBpB,YCsBoB;;ACvBpB;ECCE;;ADOF;ECJE;;ADUF;ECPE;;ADPF;ED4FE,OHtFe;;AIEjB;ED+FE,OHjGe;;AIQjB;EDoGE,OAvDiB;;AChCnB;ED+GI;;A4BpBJ;ER7GA;EAQE,qBQsG4B;;;ACxHhC;AAAA;AAAA;AAIA;EtB0DE;EACA;EACA;EACA;EACA;EAgRE,mBsB5UmB;EtB6UnB,gBsB7UmB;EtB+UrB,qBsB/UqB;EtBgVrB,kBsBhVqB;EtBiVrB,asBjVqB;EACrB,OhCOiB;EgCNjB,YhCKiB;;AgCJjB;E9BRA,oBCuBoB;EDtBpB,YCsBoB;;ACvBpB;ECCE;;ADOF;ECJE;;ADUF;ECPE;;ADPF;ED4FE,OH/Ee;;AILjB;ED+FE,OHjGe;;AIQjB;EDoGE,OAvDiB;;AChCnB;ED+GI;;A6B/HJ;EACE;EACA;;AACA;EACE;;AAEE;ETRN;EAKE,oBAPQ;ESYF;EACA;;;AAMV;E1BzBI,U0B0BgB;E1BbhB;;A0BcF;EACE;;;AC7BF;EACE;EACA;EACA;EACA;;AAGA;EACE;EACA;EACA;;;AAIN;EACE;;;AChBF;EACE;EACA,OlCuByB;;AkCtBzB;EACE;;ArCAF;EqCDA;IAGI;;;AAGJ;EACE;EACA;;ArCPF;EqCKA;IAII;;;AAGA;EXNJ;EAKE,oBAPQ;EWUJ;EACA;EACA;;;ACpBR;EACE;EACA;;AtCEA;EsCJF;IAII;;;AAEF;EACE;;AAEF;EACE;;AAEF;ErBXA,qBqBYuB;ErBXvB,kBqBWuB;ErBVvB,iBqBUuB;ErBTvB,aqBSuB;;AAEvB;EACE;;;AAKF;EACE;EACA;EACA;;AtCpBF;EsCiBA;IAKI;IACA;IACA;;;AAGJ;EACE;EAOA;;AtCnCF;EsC2BA;IAGI;;;AtC9BJ;EsC2BA;IAMI;;;AAKJ;EACE;;;AAIJ;EACE;;;AChDF;EACE;EACA;;AACA;EAHF;IAII;;;AzBHF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EyBME;;AACA;EzBPF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;IyBQI;;;AzBaJ;EyBTE;EACA;;AACA;EzBOF;IyBNI;;;AAEF;ElCjBF,oBCuBoB;EDtBpB,YCsBoB;EiCJhB;EACA;EACA;EACA;;AhCtBJ;ECCE;;ADOF;ECJE;;ADUF;ECPE;;ADPF;ED4FE,OHhDe;;AIpCjB;ED+FE,OHjGe;;AIQjB;EDoGE,OAvDiB;;AChCnB;ED+GI;;AiCnHA;EACE;;AAIF;EACE;EACA;EACA;;AAIN;AAAA;Eb1BA;EAQE,qBAVQ;;AagCV;EACE;;ArCxCF;EACE;EACA;EACA,OCqBuB;EDpBvB;EACA;EACA;;AqCqCF;EACE;EACA;EACA,OpCtBuB;EuBfzB;EAWE,mBa2B0B;;AAC1B;EACE;;AAEF;EACE;;AAGJ;EACE;EACA;;AAEF;EACE;EACA;EACA;EACA;;AAEF;EbxDA;EAEE,kBAJQ;Ea4DR;;AACA;EAHF;IAII;;;AAGJ;EACE;EACA,kBpCxCoB;EoCyCpB;;AACA;EACE;EACA;;AAGJ;E9BhFE,U8BiFkB;E9BpElB;;A8BqEA;EACE;EACA;EACA;;AACA;EAIE;;AAHA;EACE;;AAGF;EACE;;AAMN;EACE;EACA;EACA,kBpCpEkB;EoCqElB;;AACA;EACE;;AAKJ;EACE;EACA;EACA;EACA,OpCpEa;EoCqEb;EACA;EACA;EACA,kBpCpFkB;EoCqFlB;EACA;;AAEF;EACE;EACA;EACA;;AAEE;EACE;EACA;;AACA;EACE;EACA;EACA;;AAEF;E1B1ER;EACA;EACA;EACA;EACA;;A0BwEU;EACE;E1B7EZ;EACA;EACA;EACA;EACA;;A0B2EY;EACE;E9B5IZ,U8B6I8B;E9BhI9B;;A8BqIQ;EACE;;AAEF;EACE;EACA;EACA;E9BxJV,U8ByJ4B;E9B5I5B;;A8B6IU;EACE,OpCpIW;EcxBzB,qBsB6JmC;EtB5JnC,kBsB4JmC;EtB3JnC,iBsB2JmC;EtB1JnC,asB0JmC;;AAQnC;EACE;;AACA;EACE;EACA;;AAGA;EACE;;AACA;EAFF;IAGI;;;AAOJ;EACE;;AACA;EAFF;IAGI;;;AAIN;EACE;;AAEF;EACE;;AAGJ;EACE;EACA;;AACA;EACE;;AAGJ;EACE;EACA;EACA;E9B7MA,U8B8MkB;E9BjMlB;;A8BkMA;EACE,kBpC/KkB;;AoCiLpB;EACE;EACA;;AAEF;EACE;;;ACzNN;EACE;EACA;;;AAIA;EnCLA,oBCuBoB;EDtBpB,YCsBoB;;ACvBpB;ECCE;;ADOF;ECJE;;ADUF;ECPE;;ADPF;ED4FE,OHrEuB;;AIfzB;ED+FE,OHjGe;;AIQjB;EDoGE,OAvDiB;;AChCnB;ED+GI;;;AkChIN;EACE,OrCayB;;AqCZzB;EACE;;;ACbF;EACE;;AAEE;EpCHJ,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrFe;EGuFb,kBHpCa;;AGuCb;EACE,MH3FW;;AICjB;ED+FE,OHhGe;EGkGb,kBAzDe;;AA4Df;EACE,MHtGW;;AIOjB;EDoGE,OH3Ge;EG6Gb,kBA7CgB;;AAgDhB;EACE,MHjHW;;AIcjB;EDwGE,OA9HoI;EAgIlI,kBA/Ee;EAgFf;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;AmC/IJ;EpCNJ,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrFe;EGuFb,kBHnCa;;AGsCb;EACE,MH3FW;;AICjB;ED+FE,OHhGe;EGkGb,kBAzDe;;AA4Df;EACE,MHtGW;;AIOjB;EDoGE,OH3Ge;EG6Gb,kBA7CgB;;AAgDhB;EACE,MHjHW;;AIcjB;EDwGE,OA9HoI;EAgIlI,kBA/Ee;EAgFf;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;AmC5IJ;EpCTJ,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrFe;EGuFb,kBHlCa;;AGqCb;EACE,MH3FW;;AICjB;ED+FE,OHhGe;EGkGb,kBAzDe;;AA4Df;EACE,MHtGW;;AIOjB;EDoGE,OH3Ge;EG6Gb,kBA7CgB;;AAgDhB;EACE,MHjHW;;AIcjB;EDwGE,OA9HoI;EAgIlI,kBA/Ee;EAgFf;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;AmCzIJ;EpCZJ,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrFe;EGuFb,kBHjCa;;AGoCb;EACE,MH3FW;;AICjB;ED+FE,OHhGe;EGkGb,kBAzDe;;AA4Df;EACE,MHtGW;;AIOjB;EDoGE,OH3Ge;EG6Gb,kBA7CgB;;AAgDhB;EACE,MHjHW;;AIcjB;EDwGE,OA9HoI;EAgIlI,kBA/Ee;EAgFf;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;AmCtIJ;EpCfJ,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrFe;EGuFb,kBHhCa;;AGmCb;EACE,MH3FW;;AICjB;ED+FE,OHhGe;EGkGb,kBAzDe;;AA4Df;EACE,MHtGW;;AIOjB;EDoGE,OH3Ge;EG6Gb,kBA7CgB;;AAgDhB;EACE,MHjHW;;AIcjB;EDwGE,OA9HoI;EAgIlI,kBA/Ee;EAgFf;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;AmCnIJ;EpClBJ,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrFe;EGuFb,kBH/Ba;;AGkCb;EACE,MH3FW;;AICjB;ED+FE,OHhGe;EGkGb,kBAzDe;;AA4Df;EACE,MHtGW;;AIOjB;EDoGE,OH3Ge;EG6Gb,kBA7CgB;;AAgDhB;EACE,MHjHW;;AIcjB;EDwGE,OA9HoI;EAgIlI,kBA/Ee;EAgFf;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;AmChIJ;EpCrBJ,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrFe;EGuFb,kBH9Ba;;AGiCb;EACE,MH3FW;;AICjB;ED+FE,OHhGe;EGkGb,kBAtDe;;AAyDf;EACE,MHtGW;;AIOjB;EDoGE,OH3Ge;EG6Gb,kBA1CgB;;AA6ChB;EACE,MHjHW;;AIcjB;EDwGE,OA9HoI;EAgIlI,kBA5Ee;EA6Ef;;AAGA;EACE,MArIgI;;AC4BtI;EDkHI;EAGA;;AAGA;EAKI;;AmChIJ;EpCxBJ,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrFe;EGuFb,kBH7Ba;;AGgCb;EACE,MH3FW;;AICjB;ED+FE,OHhGe;EGkGb,kBAzDe;;AA4Df;EACE,MHtGW;;AIOjB;EDoGE,OH3Ge;EG6Gb,kBA7CgB;;AAgDhB;EACE,MHjHW;;AIcjB;EDwGE,OA9HoI;EAgIlI,kBA/Ee;EAgFf;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;AmC1HJ;EpC3BJ,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrFe;EGuFb,kBH5Ba;;AG+Bb;EACE,MH3FW;;AICjB;ED+FE,OHhGe;EGkGb,kBAzDe;;AA4Df;EACE,MHtGW;;AIOjB;EDoGE,OH3Ge;EG6Gb,kBA7CgB;;AAgDhB;EACE,MHjHW;;AIcjB;EDwGE,OA9HoI;EAgIlI,kBA/Ee;EAgFf;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;AmCvHJ;EpC9BJ,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrFe;EGuFb,kBH3Ba;;AG8Bb;EACE,MH3FW;;AICjB;ED+FE,OHhGe;EGkGb,kBAzDe;;AA4Df;EACE,MHtGW;;AIOjB;EDoGE,OH3Ge;EG6Gb,kBA7CgB;;AAgDhB;EACE,MHjHW;;AIcjB;EDwGE,OA9HoI;EAgIlI,kBA/Ee;EAgFf;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;AmCpHJ;EpCjCJ,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrFe;EGuFb,kBH1Ba;;AG6Bb;EACE,MH3FW;;AICjB;ED+FE,OHhGe;EGkGb,kBAtDe;;AAyDf;EACE,MHtGW;;AIOjB;EDoGE,OH3Ge;EG6Gb,kBA1CgB;;AA6ChB;EACE,MHjHW;;AIcjB;EDwGE,OA9HoI;EAgIlI,kBA5Ee;EA6Ef;;AAGA;EACE,MArIgI;;AC4BtI;EDkHI;EAGA;;AAGA;EAKI;;AmCpHJ;EpCpCJ,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrFe;EGuFb,kBHzBa;;AG4Bb;EACE,MH3FW;;AICjB;ED+FE,OHhGe;EGkGb,kBAzDe;;AA4Df;EACE,MHtGW;;AIOjB;EDoGE,OH3Ge;EG6Gb,kBA7CgB;;AAgDhB;EACE,MHjHW;;AIcjB;EDwGE,OA9HoI;EAgIlI,kBA/Ee;EAgFf;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;;AoCtJV;EACE;EACA;EACA;EACA;EACA,kBvC8BsB;;AH/BtB;E0CJF;IAOI;;;;AAGJ;EACE;EACA;EACA;EACA;;;AAEF;EACE;EACA;;AACA;ErClBA,oBCuBoB;EDtBpB,YCsBoB;;ACvBpB;ECCE;;ADOF;ECJE;;ADUF;ECPE;;ADPF;ED4FE,OHrEuB;;AIfzB;ED+FE,OHjGe;;AIQjB;EDoGE,OAvDiB;;AChCnB;ED+GI;;;AoCpHN;EjCrBI,UiCsBgB;EjCThB;;;AkCdF;EtCAA,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OH/Ee;EGiFb,kBHlFa;;AGqFb;EACE,MHrFW;;AILjB;ED+FE,OH1Fe;EG4Fb,kBAzDe;;AA4Df;EACE,MHhGW;;AICjB;EDoGE,OHxGe;EG0Gb,kBH3Ga;;AG8Gb;EACE,MH9GW;;AIWjB;EDwGE,OHnHe;EGqHb,kBHtHa;EGuHb;;AAGA;EACE,MH1HW;;AIiBjB;ED+GI;EAMA;;AAGA;EAEI;;AqCnJN;EACE;EACA;EACA;EACA;EACA;;AAGJ;EtCVA,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrFe;EGuFb,kBqCnFgC;;ArCsFhC;EACE,MH3FW;;AICjB;ED+FE,OHhGe;EGkGb,kBAzDe;;AA4Df;EACE,MHtGW;;AIOjB;EDoGE,OHxGe;EG0Gb,kBH3Ga;;AG8Gb;EACE,MH9GW;;AIWjB;EDwGE,OHnHe;EGqHb,kBHtHa;EGuHb;;AAGA;EACE,MH1HW;;AIiBjB;ED+GI;EAMA;;AAGA;EAEI;;AqCxIR;EtCbA,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrFe;EGuFb,kBqChFgC;;ArCmFhC;EACE,MH3FW;;AICjB;ED+FE,OHhGe;EGkGb,kBAzDe;;AA4Df;EACE,MHtGW;;AIOjB;EDoGE,OHxGe;EG0Gb,kBH3Ga;;AG8Gb;EACE,MH9GW;;AIWjB;EDwGE,OHnHe;EGqHb,kBHtHa;EGuHb;;AAGA;EACE,MH1HW;;AIiBjB;ED+GI;EAMA;;AAGA;EAEI;;AqCrIR;EtChBA,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrFe;EGuFb,kBqC7EgC;;ArCgFhC;EACE,MH3FW;;AICjB;ED+FE,OHhGe;EGkGb,kBAzDe;;AA4Df;EACE,MHtGW;;AIOjB;EDoGE,OHxGe;EG0Gb,kBH3Ga;;AG8Gb;EACE,MH9GW;;AIWjB;EDwGE,OHnHe;EGqHb,kBHtHa;EGuHb;;AAGA;EACE,MH1HW;;AIiBjB;ED+GI;EAMA;;AAGA;EAEI;;AqClIR;EtCnBA,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrFe;EGuFb,kBqC1EgC;;ArC6EhC;EACE,MH3FW;;AICjB;ED+FE,OHhGe;EGkGb,kBAzDe;;AA4Df;EACE,MHtGW;;AIOjB;EDoGE,OHxGe;EG0Gb,kBH3Ga;;AG8Gb;EACE,MH9GW;;AIWjB;EDwGE,OHnHe;EGqHb,kBHtHa;EGuHb;;AAGA;EACE,MH1HW;;AIiBjB;ED+GI;EAMA;;AAGA;EAEI;;;AsCtJV;EnCEI,UmCDgB;EnCchB;;;AmCVF;EACE;EACA;;A5CHF;E4CCA;IAII;IACA;;;;AAKN;EACE;EACA;EACA;EACA,OzCIyB;;AyCHzB;EACE,OzCEuB;;AyCAzB;EACE,OzCGuB;;AHvBzB;E4CWF;IAYI;;;;AAIJ;E/B+BE;EACA;EACA;EACA;EACA;E+BjCA;;;AAGF;EACE;EACA;EACA;;A5CnCA;E4CgCF;IAKI;IACA;;;AAEF;EACE;EACA;EACA,QC+DsB;ED9DtB;EACA;EACA;EACA,OzC3BuB;EyC4BvB;EACA;EACA;EACA;EvCtDF,oBuCuDsB;EvCtDtB,YuCsDsB;;ArClCtB;EqCoCI;;AAEF;EACE,OzCrCqB;EyCsCrB,czCtCqB;;AIFzB;EqC0CM;;AAGJ;EACE,OzCxCqB;EyCyCrB,czCzCqB;;AINzB;EqCiDM;;AAIN;EACE,OzCnDuB;;AyCoDvB;EACE,OzCrDqB;;AyCuDvB;EACE,OzCpDqB;;AyCwDvB;EvCpFF,oBCuBoB;EDtBpB,YCsBoB;EsCqEhB;;ArC5FJ;ECCE;;ADOF;ECJE;;ADUF;ECPE;;ADPF;ED4FE,OHrEuB;;AIfzB;ED+FE,OAhEgB;;ACzBlB;EDoGE,OApDiB;;ACnCnB;EDkHI;;AsCvDA;EvCtFJ,oBCuBoB;EDtBpB,YCsBoB;;ACvBpB;ECCE;;ADOF;ECJE;;ADUF;ECPE;;ADPF;ED4FE,OHrEuB;;AIfzB;ED+FE,OAhEgB;;ACzBlB;EDoGE,OApDiB;;ACnCnB;EDkHI;;AsCpDA;EvCzFJ,oBCuBoB;EDtBpB,YCsBoB;;ACvBpB;ECCE;;ADOF;ECJE;;ADUF;ECPE;;ADPF;ED4FE,OHjEuB;;AInBzB;ED+FE,OAnEgB;;ACtBlB;EDoGE,OAvDiB;;AChCnB;ED+GI;;AsC3CJ;EACE;EACA,OCYsB;EDXtB,QCWsB;EDVtB,aCUsB;EDTtB;EACA;;AAEF;EACE;;AAEF;EACE;EACA;EACA;;AAEF;EACE;EACA;;;AAIJ;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA,OzC7GyB;EyC8GzB;;AACA;EACE,OzChHuB;;AyCkHzB;EACE,OzC/GuB;;;AyCmH3B;EACE;;AACA;EACE;EvClJF,oBuCmJsB;EvClJtB,YuCkJsB;EvCnJtB,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrEuB;EGuErB,kBsCsD8B;;AtCnD9B;EACE,MH3EmB;;AIfzB;ED+FE,OH1Fe;EG4Fb,kBH7Fa;;AGgGb;EACE,MHhGW;;AICjB;EDoGE,OHrGe;EGuGb,kBA7CgB;;AAgDhB;EACE,MH3GW;;AIQjB;EDwGE,OA9HoI;EAgIlI,kBHnHa;EGoHb;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;AsCAN;EvCrJF,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHrEuB;EGuErB,kBsCwD4C;;AtCrD5C;EACE,MH3EmB;;AIfzB;ED+FE,OH5EuB;EG8ErB,kBH1FmB;;AG6FnB;EACE,MHlFmB;;AIbzB;EDoGE,OHvFuB;EGyFrB,kBA1CgB;;AA6ChB;EACE,MH7FmB;;AINzB;EDwGE,OA9HoI;EAgIlI,kBHhHmB;EGiHnB;;AAGA;EACE,MArIgI;;AC4BtI;EDkHI;EAGA;;AAGA;EAKI;;AsCAN;EvCxJF,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHjEuB;EGmErB,kBsC2D2C;;AtCxD3C;EACE,MHvEmB;;AInBzB;ED+FE,OHhFuB;EGkFrB,kBHzFmB;;AG4FnB;EACE,MHtFmB;;AITzB;EDoGE,OH3FuB;EG6FrB,kBA7CgB;;AAgDhB;EACE,MHjGmB;;AIFzB;EDwGE,OA9HoI;EAgIlI,kBH/GmB;EGgHnB;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;ACrJR;EqC+JM,OzClJW;EyCmJX,kBzCpJW;;AyCqJX;EACE,OzCvIiB;EyCwIjB,kBzCpJe;;AyCsJjB;EACE,OzC/IiB;EyCgJjB,kBzCvJe;;AIFvB;EFdA,oBuC2K0B;EvC1K1B,YuC0K0B;;;AAQ1B;AAAA;AAAA;EAGE;;;AEvLJ;EACE;EzCAA,oByCIoB;EzCHpB,YyCGoB;;AvCIpB;EGEE;;;AqCXJ;EACE;;;AAIF;EACE;EACA;EACA;;AACA;EACE;EACA;EACA;EACA;EACA;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AChCA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;;ACzBF;EACE,O9CsDkB;E8CrDlB,kB9CiDc;;A8C9ChB;EACE,O9CiDkB;E8ChDlB,kB9C6Cc;;A8C1ChB;EACE,O9C4CkB;E8C3ClB,kB9CyCc;;A8CtChB;EACE,O9CuCkB;E8CtClB,kB9CqCc;;;A+CtDhB;ExCOE;;AwCHF;EACE;;AAGF;EACE;;AAGF;EACE;;;ACdJ;EACE;;AACA;EACE;;;ACHJ;AAAA;AAAA;AAAA;EAIE;;;AAKE;AAAA;EAEE;;ApDPJ;EoDUI;I3CZF,U2CasB;;;;AAM1B;EACE;EACA,OjDCyB;;AiDAzB;EACE;;AACA;EACE;EACA;E3C1BF,U2C2BoB;;;AAKxB;EACE;EvC2BA;EACA;EACA;EACA;EACA;EAmCE;EACA;EAQF,wBuCzEwB;EvC0ExB,qBuC1EwB;EvC2ExB,oBuC3EwB;EvC4ExB,gBuC5EwB;EACxB;EACA,kBjDnBiB;;;AiDsBnB;EvC8NE,kBuC7Nc;EvC8Nd;EACA,euC/Nc;EvCgOd;EACA;EACA;EuCjOA;EACA;;AACA;EAJF;IAKI;;;;AAIF;EACE;;;AAIJ;EACE;;;AAIA;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA,kBjD5DiB;EuBVjB;EAKE,oBAPQ;ErBPV,oB+CiFoB;E/ChFpB,Y+CgFoB;E3ChFlB,U2CiFgB;E3CpEhB;;A2CqEF;EACE;;;AAGJ;E/CvFE,oBCuBoB;EDtBpB,YCsBoB;;AAGhB;ED1BJ,oBC2B0B;ED1B1B,YC0B0B;;AC3B1B;ED4FE,OHtEuB;EGwErB,kB8CN8B;;A9CS9B;EACE,MH5EmB;;AIdzB;ED+FE,OHjFuB;EGmFrB,kBAzDe;;AA4Df;EACE,MHvFmB;;AIRzB;EDoGE,OH5FuB;EG8FrB,kBA7CgB;;AAgDhB;EACE,MHlGmB;;AIDzB;EDwGE,OA9HoI;EAgIlI,kBA/Ee;EAgFf;;AAGA;EACE,MArIgI;;AC4BtI;ED+GI;EAMA;;AAGA;EAEI;;;A8C1DV;E7BlFE;EACA;EACA;EACA,S6BgFe;E7B/Ef;EACA;EACA,OpBYyB;EoBXzB;EACA,kBpB8BiB;EoB7BjB;EClBA,mBDmBmB;EClBnB,WDkBmB;ElBnBnB,oBkBoBoB;ElBnBpB,YkBmBoB;E6ByEpB;;;AAGA;E3C/FE,U2CgGkB;E3CnFlB;;A2CoFA;EAFF;I3C/FE,U2CkGoB;;;;AAMtB;EACE;EACA;EACA;;AAEE;EACE;EACA;;ApD7GN;EoDsGA;IAWI;;;;ApDjHJ;EoDuHA;IACE;I5B3HF,mB4B4HqB;I5B3HrB,W4B2HqB;;EAGrB;IACE;;EAIA;IACE;;EAEF;I5BvIF,mB4BwIuB;I5BvIvB,W4BuIuB;;EAErB;I7BlHF;ICxBA,mBDyBmB;ICxBnB,WDwBmB;IlBzBnB,oBkB0BoB;IlBzBpB,YkByBoB;;;A6BuHpB;EACE,OjD3HuB;;;AiD+HzB;EACE,OjD5HuB;;;AiDiIzB;EACE;EACA;;AAEF;EACE;;;AClKJ;EACE;;;AAEF;EACE;EACA;EACA;E3BIA;EAEE,kB2BLuB;;AACzB;EACE;;AACA;EACE;EACA,OlDaqB;;AkDXvB;EACE;EhDdJ,oBCuBoB;EDtBpB,YCsBoB;;ACvBpB;ECCE;;ADOF;ECJE;;ADUF;ECPE;;ADPF;ED4FE,OHrEuB;;AIfzB;ED+FE,OHjGe;;AIQjB;EDoGE,OAvDiB;;AChCnB;ED+GI;;A+CxHJ;EACE;EACA;;AAEF;EACE;EACA;EACA;;;AC1BJ;EACE;EACA;;AtDEA;EsDJF;IAII;;;AAEF;EACE;;AAIE;EACE;;;ACXN;EACE;;;ACDF;EACE;;AAEF;EACE;E/CHA,U+CIkB;E/CSlB;;A+CPF;EACE,OrDgBuB;;AqDdzB;EACE;;;ACXF;EACE;EACA;EACA;;AAGA;EACE;EACA;EACA;;AAGJ;EACE;;AAEF;EACE;;AAEF;EACE;;;ACnBF;EACE;EACA;EACA;;AACA;EACE;EACA;;AAEF;EACE;;;ACVN;AAEA","sourcesContent":["figure.highlight::before {\n color: $highlight-comment !important;\n background-color: $highlight-background !important;\n}\npre.lineno {\n color: $highlight-comment !important;\n}\n.highlight > pre {\n color: $highlight-foreground;\n background-color: $highlight-background !important;\n .c { color: $highlight-comment; } /* Comment */\n .err { color: $highlight-red; } /* Error */\n .k { color: $highlight-purple; } /* Keyword */\n .l { color: $highlight-orange; } /* Literal */\n .n { color: $highlight-foreground; } /* Name */\n .o { color: $highlight-aqua; } /* Operator */\n .p { color: $highlight-foreground; } /* Punctuation */\n .cm { color: $highlight-comment; } /* Comment.Multiline */\n .cp { color: $highlight-comment; } /* Comment.Preproc */\n .c1 { color: $highlight-comment; } /* Comment.Single */\n .cs { color: $highlight-comment; } /* Comment.Special */\n .gd { color: $highlight-red; } /* Generic.Deleted */\n .ge { font-style: italic; } /* Generic.Emph */\n .gh { font-weight: bold; color: $highlight-foreground; } /* Generic.Heading */\n .gi { color: $highlight-green; } /* Generic.Inserted */\n .gp { font-weight: bold; color: $highlight-comment; } /* Generic.Prompt */\n .gs { font-weight: bold; } /* Generic.Strong */\n .gu { font-weight: bold; color: $highlight-aqua; } /* Generic.Subheading */\n .kc { color: $highlight-purple; } /* Keyword.Constant */\n .kd { color: $highlight-purple; } /* Keyword.Declaration */\n .kn { color: $highlight-aqua; } /* Keyword.Namespace */\n .kp { color: $highlight-purple; } /* Keyword.Pseudo */\n .kr { color: $highlight-purple; } /* Keyword.Reserved */\n .kt { color: $highlight-yellow; } /* Keyword.Type */\n .ld { color: $highlight-green; } /* Literal.Date */\n .m { color: $highlight-orange; } /* Literal.Number */\n .s { color: $highlight-green; } /* Literal.String */\n .na { color: $highlight-blue; } /* Name.Attribute */\n .nb { color: $highlight-foreground; } /* Name.Builtin */\n .nc { color: $highlight-yellow; } /* Name.Class */\n .no { color: $highlight-red; } /* Name.Constant */\n .nd { color: $highlight-aqua; } /* Name.Decorator */\n .ni { color: $highlight-foreground; } /* Name.Entity */\n .ne { color: $highlight-red; } /* Name.Exception */\n .nf { color: $highlight-blue; } /* Name.Function */\n .nl { color: $highlight-foreground; } /* Name.Label */\n .nn { color: $highlight-yellow; } /* Name.Namespace */\n .nx { color: $highlight-blue; } /* Name.Other */\n .py { color: $highlight-foreground; } /* Name.Property */\n .nt { color: $highlight-aqua; } /* Name.Tag */\n .nv { color: $highlight-red; } /* Name.Variable */\n .ow { color: $highlight-aqua; } /* Operator.Word */\n .w { color: $highlight-foreground; } /* Text.Whitespace */\n .mf { color: $highlight-orange; } /* Literal.Number.Float */\n .mh { color: $highlight-orange; } /* Literal.Number.Hex */\n .mi { color: $highlight-orange; } /* Literal.Number.Integer */\n .mo { color: $highlight-orange; } /* Literal.Number.Oct */\n .sb { color: $highlight-green; } /* Literal.String.Backtick */\n .sc { color: $highlight-foreground; } /* Literal.String.Char */\n .sd { color: $highlight-comment; } /* Literal.String.Doc */\n .s2 { color: $highlight-green; } /* Literal.String.Double */\n .se { color: $highlight-orange; } /* Literal.String.Escape */\n .sh { color: $highlight-green; } /* Literal.String.Heredoc */\n .si { color: $highlight-orange; } /* Literal.String.Interpol */\n .sx { color: $highlight-green; } /* Literal.String.Other */\n .sr { color: $highlight-green; } /* Literal.String.Regex */\n .s1 { color: $highlight-green; } /* Literal.String.Single */\n .ss { color: $highlight-green; } /* Literal.String.Symbol */\n .bp { color: $highlight-foreground; } /* Name.Builtin.Pseudo */\n .vc { color: $highlight-red; } /* Name.Variable.Class */\n .vg { color: $highlight-red; } /* Name.Variable.Global */\n .vi { color: $highlight-red; } /* Name.Variable.Instance */\n .il { color: $highlight-orange; } /* Literal.Number.Integer.Long */\n}\n","$highlight-background : #f7f7f7;\n$highlight-foreground : #4d4d4c;\n$highlight-comment : #8e908c;\n$highlight-red : #c82829;\n$highlight-orange : #f5871f;\n$highlight-yellow : #eab700;\n$highlight-green : #718c00;\n$highlight-aqua : #3e999f;\n$highlight-blue : #4271ae;\n$highlight-purple : #8959a8;\n","/* stylelint-disable at-rule-name-space-after, at-rule-semicolon-space-before */\n@charset \"utf-8\";\n@import\n \"common/classes/animation\",\n \"common/classes/transform\",\n \"common/classes/transition\",\n \"common/classes/user-select\",\n\n \"common/classes/clearfix\",\n \"common/classes/media\",\n \"common/classes/clickable\",\n \"common/classes/display\",\n \"common/classes/flex\",\n \"common/classes/horizontal-rules\",\n \"common/classes/pseudo\",\n \"common/classes/link\",\n \"common/classes/text\",\n \"common/classes/overflow\",\n \"common/classes/shadow\",\n \"common/classes/spacing\",\n \"common/classes/split-line\",\n \"common/classes/grid\"\n;\n/* stylelint-enable */\n","@mixin clearfix() {\n &::after {\n display: table;\n clear: both;\n content: \"\";\n }\n}\n\n.clearfix {\n @include clearfix();\n}\n\n.left {\n float: left;\n}\n\n.right {\n float: right;\n}\n","@mixin media-breakpoint-down($name, $breakpoints: default) {\n @if $breakpoints == default {\n $breakpoints: $responsive;\n }\n @media (max-width: map-get($breakpoints, $name) - 1) {\n @content;\n }\n}\n\n@mixin media-breakpoint-up($name, $breakpoints: default) {\n @if $breakpoints == default {\n $breakpoints: $responsive;\n }\n @media (min-width: map-get($breakpoints, $name)) {\n @content;\n }\n}\n","@each $breakpoint in map-keys($responsive) {\n @include media-breakpoint-up($breakpoint) {\n .d-#{breakpoint-infix($breakpoint)}none {\n display: none !important;\n }\n }\n}\n\n.d-print-none {\n @media print {\n display: none !important;\n }\n}\n","@mixin horizontal-rules() {\n &::before {\n display: block;\n font-size: map-get($base, font-size-h2);\n color: $text-color-l;\n text-align: center;\n letter-spacing: map-get($spacers, 4);\n content: \"...\";\n }\n}\n\n.horizontal-rules {\n @include horizontal-rules();\n}\n","///\n// Skin: Default\n// Author: Tian Qi\n// Email: kitian616@outlook.com\n///\n\n// main colors\n$main-color-1: #fc4d50;\n$text-color-1: #fff;\n\n$main-color-2: #fca24d;\n$text-color-2: #fff;\n\n$main-color-3: #f2f2f2;\n$text-color-3: #333;\n\n$main-color-theme-light: rgba(#000, .9);\n$main-color-theme-dark: rgba(#fff, .9);\n\n// page background\n$background-color: #fff;\n\n// text colors\n$text-color-theme-light-d: #000;\n$text-color-theme-light: #222;\n$text-color-theme-light-l: #888;\n\n$text-color-theme-dark-d: #fff;\n$text-color-theme-dark: rgba(#fff, .95);\n$text-color-theme-dark-l: rgba(#fff, .85);\n\n$text-color-d: $text-color-theme-light-d;\n$text-color: $text-color-theme-light;\n$text-color-l: $text-color-theme-light-l;\n\n$text-background-color: rgba(#000, .05);\n\n// header and footer colors\n$header-text-color: $text-color-3;\n$header-background: $main-color-3;\n\n$footer-text-color: $text-color-3;\n$footer-background: $main-color-3;\n\n// border and shadow colors\n$border-color: mix(#000, $background-color, 20%);\n$border-color-l: mix(#000, $background-color, 10%);\n$decorate-color: rgba(#000, .1);\n$mask-color: rgba(#000, .9);\n$select-color: rgba($main-color-1, .5);\n\n// function colors\n$green: #52c41a;\n$blue: #1890ff;\n$yellow: #fa8c16;\n$red: #f5222d;\n$text-color-function: #fff;\n\n// logo colors\n$mail-color: #0072c5;\n$facebook-color: #4267b2;\n$twitter-color: #1da1f2;\n$weibo-color: #e6162d;\n$google-plus-color:#ea4335;\n$telegram-color: #32afed;\n$medium-color: #000;\n$zhihu-color: #0084ff;\n$douban-color: #42bd56;\n$linkedin-color: #1074af;\n$github-color: #000;\n$npm-color: #fff;\n\n// highlight colors\n@import \"skins/highlight/tomorrow\";\n","@mixin text-light {\n color: $text-color-theme-light;\n h1, h2, h3 {\n color: $text-color-theme-light-d;\n }\n h4, h5 {\n color: $text-color-theme-light;\n }\n h6 {\n color: $text-color-theme-light-l;\n }\n a:not(.button) {\n @include link-colors($text-color-theme-light, $main-color-1);\n }\n}\n@mixin text-dark {\n color: $text-color-theme-dark;\n h1, h2, h3 {\n color: $text-color-theme-dark-d;\n }\n h4, h5 {\n color: $text-color-theme-dark;\n }\n h6 {\n color: $text-color-theme-dark-l;\n }\n a:not(.button) {\n @include link-colors($text-color-theme-dark, $main-color-1);\n }\n}\n\n.text--light {\n @include text-light();\n}\n.text--dark {\n @include text-dark();\n}\n","@mixin transition($value) {\n -webkit-transition: $value;\n transition: $value;\n}\n","@mixin clickable($clr, $bg-clr, $hover-clr: default, $hover-bg-clr: default, $active-clr: default, $active-bg-clr: default, $focus-clr: default, $focus-bg-clr: default, $theme: default, $ignore-path: default) {\n\n @if $theme == default {\n @if $bg-clr == null and $hover-bg-clr == null {\n @if $hover-clr == default {\n $theme: get-color-theme($clr);\n } @else {\n $theme: get-color-theme($hover-clr);\n }\n } @else {\n @if $hover-bg-clr == default {\n $theme: get-color-theme($bg-clr);\n } @else {\n $theme: get-color-theme($hover-bg-clr);\n }\n }\n }\n\n @if $ignore-path == default {\n $ignore-path: false;\n } @else {\n $ignore-path: true;\n }\n\n @include transition(map-get($clickable, transition));\n @if $ignore-path == false {\n svg {\n path {\n @include transition(map-get($clickable, transition));\n }\n }\n }\n\n // hover\n @if $hover-clr == default {\n @if $hover-bg-clr == null {\n @if $theme == \"light\" {\n $hover-clr: darken($clr, 14%);\n }\n @if $theme == \"dark\" {\n $hover-clr: lighten($clr, 18%);\n }\n } @else if $hover-bg-clr == default {\n $hover-clr: $clr;\n }\n }\n\n @if $hover-bg-clr == default {\n @if $theme == \"light\" {\n $hover-bg-clr: darken($bg-clr, 14%);\n }\n @if $theme == \"dark\" {\n $hover-bg-clr: lighten($bg-clr, 18%);\n }\n }\n\n // active\n @if $active-clr == default {\n @if $active-bg-clr == null {\n @if $theme == \"light\" {\n $active-clr: darken($hover-clr, 15%);\n }\n @if $theme == \"dark\" {\n $active-clr: lighten($hover-clr, 16%);\n }\n } @else if $active-bg-clr == default {\n $active-clr: $hover-clr;\n }\n }\n\n @if $active-bg-clr == default {\n @if $theme == \"light\" {\n $active-bg-clr: darken($hover-bg-clr, 15%);\n }\n @if $theme == \"dark\" {\n $active-bg-clr: lighten($hover-bg-clr, 16%);\n }\n }\n\n // focus\n @if $focus-clr == default {\n @if $focus-bg-clr == null {\n $focus-clr: $hover-clr;\n } @else if $hover-bg-clr == default {\n $focus-clr: $hover-clr;\n }\n }\n\n @if $focus-bg-clr == default {\n $focus-bg-clr: $hover-bg-clr;\n }\n\n @include plain() {\n color: $clr;\n @if $bg-clr {\n background-color: $bg-clr;\n }\n @if $ignore-path == false {\n svg path {\n fill: $clr;\n }\n }\n }\n @include hover() {\n color: $hover-clr;\n @if $hover-bg-clr {\n background-color: $hover-bg-clr;\n }\n @if $ignore-path == false {\n svg path {\n fill: $hover-clr;\n }\n }\n }\n @include active() {\n color: $active-clr;\n @if $active-bg-clr {\n background-color: $active-bg-clr;\n }\n @if $ignore-path == false {\n svg path {\n fill: $active-clr;\n }\n }\n }\n @include focus() {\n color: $focus-clr;\n @if $focus-bg-clr{\n background-color: $focus-bg-clr;\n box-shadow: 0 0 0 2px rgba($focus-bg-clr, .4);\n }\n @if $ignore-path == false {\n svg path {\n fill: $focus-clr;\n }\n }\n }\n @include disabled() {\n @if $theme == \"light\" {\n color: rgba($clr, .2) !important;\n }\n @if $theme == \"dark\" {\n color: rgba($clr, .4) !important;\n }\n @if $bg-clr {\n background-color: $bg-clr !important;\n }\n @if $ignore-path == false {\n svg path {\n @if $theme == \"light\" {\n fill: rgba($clr, .2) !important;\n }\n @if $theme == \"dark\" {\n fill: rgba($clr, .4) !important;\n }\n }\n }\n }\n}\n","@mixin plain() {\n &,\n &:link,\n &:visited {\n @content;\n }\n}\n\n@mixin hover() {\n .root[data-is-touch=\"false\"] &:hover {\n @content;\n }\n}\n\n@mixin active() {\n .root[data-is-touch] &.active,\n .root[data-is-touch] &:active {\n @content;\n }\n}\n\n@mixin focus() {\n .root[data-is-touch] &.focus {\n @content;\n }\n}\n\n@mixin disabled() {\n &.disabled,\n &:disabled {\n @content;\n }\n}\n","@mixin link-colors($clr, $hover-clr: default, $active-clr: default, $focus-clr: null, $theme: default, $ignore-path: false) {\n @include plain() {\n text-decoration: none;\n }\n @include hover() {\n text-decoration: underline;\n }\n @include active() {\n text-decoration: none;\n }\n @include clickable($clr, null, $hover-clr, null, $active-clr, null, $focus-clr, null, $theme, $ignore-path);\n}\n","@mixin overflow($overflow: auto, $direction: default) {\n @if $direction == default {\n overflow: $overflow;\n } @else if $direction == \"x\" {\n @if $overflow == auto {\n overflow: hidden;\n }\n overflow-x: $overflow;\n } @else if $direction == \"y\" {\n @if $overflow == auto {\n overflow: hidden;\n }\n overflow-y: $overflow;\n }\n @if $overflow == auto {\n -webkit-overflow-scrolling: touch;\n }\n}\n\n.of-auto {\n @include overflow(auto);\n}\n\n.of-hidden {\n @include overflow(hidden);\n}\n","@mixin box-shadow($level: default, $color: default) {\n @if $color == default {\n $color: #000;\n }\n @if $level == 0 {\n box-shadow: none;\n }\n @if $level == 1 or $level == default {\n box-shadow: 0 4px 8px rgba($color, .23), 0 1px 3px rgba($color, .08), 0 6px 12px rgba($color, .02);\n }\n @if $level == 2 {\n box-shadow: 0 8px 16px rgba($color, .23), 0 2px 6px rgba($color, .08), 0 12px 24px rgba($color, .02);\n }\n}\n\n.box-shadow-1 {\n @include box-shadow();\n}\n\n.box-shadow-2 {\n @include box-shadow(2);\n}\n","@mixin make-spacing($property, $side, $spacer, $negative: false) {\n\n $css_property: null;\n $css_sides: null;\n\n @if ($property == \"m\") {\n $css_property: \"margin\";\n } @else if ($property == \"p\") {\n $css_property: \"padding\";\n }\n\n @if ($side == \"t\") {\n $css_sides: (\"top\");\n }\n @else if ($side == \"b\") {\n $css_sides: (\"bottom\");\n }\n @else if ($side == \"l\") {\n $css_sides: (\"left\");\n }\n @else if ($side == \"r\") {\n $css_sides: (\"right\");\n }\n @else if ($side == \"x\") {\n $css_sides: (\"left\", \"right\");\n }\n @else if ($side == \"y\") {\n $css_sides: (\"top\", \"bottom\");\n }\n @else if ($side == \"\") {\n $css_sides: (\"\");\n }\n\n @each $side in $css_sides {\n @if ($spacer == \"auto\") {\n @if ($side == \"\") {\n #{$css_property}: auto;\n } @else {\n #{$css_property}-#{$side}: auto;\n }\n } @else {\n @if ($side == \"\") {\n @if ($negative == true) {\n #{$css_property}: - map-get($spacers, $spacer);\n } @else {\n #{$css_property}: map-get($spacers, $spacer);\n }\n } @else {\n @if ($negative == true) {\n #{$css_property}-#{$side}: - map-get($spacers, $spacer);\n } @else {\n #{$css_property}-#{$side}: map-get($spacers, $spacer);\n }\n }\n }\n }\n}\n\n@mixin make-spacings() {\n $propertys: (\"m\", \"p\");\n $sides: (\"t\", \"b\", \"l\", \"r\", \"x\", \"y\", \"\");\n $spacers: (0, 1, 2, 3, 4, 5);\n\n @each $property in $propertys {\n @each $side in $sides {\n @each $spacer in $spacers {\n .#{$property}#{$side}-#{$spacer} {\n @include make-spacing($property, $side, $spacer);\n }\n }\n }\n }\n\n @each $side in $sides {\n .m#{$side}-auto {\n @include make-spacing(\"m\", $side, \"auto\");\n }\n }\n}\n\n@include make-spacings();\n","$grid-columns: 12;\n\n.grid-container {\n @include overflow(hidden);\n}\n.cell {\n min-width: 0;\n}\n\n@mixin make-cell($columns) {\n @if $columns == \"auto\" {\n @include flex(1 1 0);\n width: auto;\n } @else if $columns == \"shrink\" {\n @include flex(0 0 auto);\n width: auto;\n } @else if $columns == \"stretch\" {\n @include flex(1);\n } @else {\n @include flex(none);\n width: percentage($columns / $grid-columns);\n }\n}\n\n@mixin make-grid-cell($columns, $breakpoint) {\n @include media-breakpoint-up($breakpoint) {\n .cell--#{breakpoint-infix($breakpoint)}#{$columns} {\n @include make-cell($columns);\n }\n }\n}\n\n.grid {\n @include flexbox();\n @include flex-wrap(wrap);\n & > {\n @each $breakpoint in map-keys($responsive) {\n @for $i from 1 through $grid-columns {\n @include make-grid-cell($i, $breakpoint);\n }\n @include make-grid-cell(\"auto\", $breakpoint);\n @include make-grid-cell(\"shrink\", $breakpoint);\n @include make-grid-cell(\"stretch\", $breakpoint);\n }\n }\n}\n\n.grid--reverse {\n flex-direction: row-reverse;\n}\n\n@mixin make-grid() {\n $types: (\"p\");\n $directions: (\"x\", \"y\", \"\");\n $spacers: (0, 1, 2, 3, 4, 5);\n\n @each $type in $types {\n @each $direction in $directions {\n @each $spacer in $spacers {\n @if $direction == \"\" {\n .grid--#{$type}-#{$spacer} {\n @include make-spacing(\"m\", \"\", $spacer, true);\n .cell {\n @include make-spacing($type, \"\", $spacer);\n }\n }\n } @else {\n .grid--#{$type}#{$direction}-#{$spacer} {\n @include make-spacing(\"m\", $direction, $spacer, true);\n .cell {\n @include make-spacing($type, $direction, $spacer);\n }\n }\n }\n }\n }\n }\n}\n\n@include make-grid();\n","// Flexbox Mixins\n// http://philipwalton.github.io/solved-by-flexbox/\n// https://github.com/philipwalton/solved-by-flexbox\n//\n// Copyright (c) 2013 Brian Franco\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n//\n// This is a set of mixins for those who want to mess around with flexbox\n// using the native support of current browsers. For full support table\n// check: http://caniuse.com/flexbox\n//\n// Basically this will use:\n//\n// * Fallback, old syntax (IE10, mobile webkit browsers - no wrapping)\n// * Final standards syntax (FF, Safari, Chrome, IE11, Opera)\n//\n// This was inspired by:\n//\n// * http://dev.opera.com/articles/view/advanced-cross-browser-flexbox/\n//\n// With help from:\n//\n// * http://w3.org/tr/css3-flexbox/\n// * http://the-echoplex.net/flexyboxes/\n// * http://msdn.microsoft.com/en-us/library/ie/hh772069(v=vs.85).aspx\n// * http://css-tricks.com/using-flexbox/\n// * http://dev.opera.com/articles/view/advanced-cross-browser-flexbox/\n// * https://developer.mozilla.org/en-us/docs/web/guide/css/flexible_boxes\n\n//----------------------------------------------------------------------\n\n// Flexbox Containers\n//\n// The 'flex' value causes an element to generate a block-level flex\n// container box.\n//\n// The 'inline-flex' value causes an element to generate a inline-level\n// flex container box.\n//\n// display: flex | inline-flex\n//\n// http://w3.org/tr/css3-flexbox/#flex-containers\n//\n// (Placeholder selectors for each type, for those who rather @extend)\n\n@mixin flexbox {\n display: -webkit-box;\n display: -webkit-flex;\n display: -moz-flex;\n display: -ms-flexbox;\n display: flex;\n}\n\n%flexbox { @include flexbox; }\n\n//----------------------------------\n\n@mixin inline-flex {\n display: -webkit-inline-box;\n display: -webkit-inline-flex;\n display: -moz-inline-flex;\n display: -ms-inline-flexbox;\n display: inline-flex;\n}\n\n%inline-flex { @include inline-flex; }\n\n//----------------------------------------------------------------------\n\n// Flexbox Direction\n//\n// The 'flex-direction' property specifies how flex items are placed in\n// the flex container, by setting the direction of the flex container's\n// main axis. This determines the direction that flex items are laid out in.\n//\n// Values: row | row-reverse | column | column-reverse\n// Default: row\n//\n// http://w3.org/tr/css3-flexbox/#flex-direction-property\n\n@mixin flex-direction($value: row) {\n @if $value == row-reverse {\n -webkit-box-direction: reverse;\n -webkit-box-orient: horizontal;\n } @else if $value == column {\n -webkit-box-direction: normal;\n -webkit-box-orient: vertical;\n } @else if $value == column-reverse {\n -webkit-box-direction: reverse;\n -webkit-box-orient: vertical;\n } @else {\n -webkit-box-direction: normal;\n -webkit-box-orient: horizontal;\n }\n -webkit-flex-direction: $value;\n -moz-flex-direction: $value;\n -ms-flex-direction: $value;\n flex-direction: $value;\n}\n// Shorter version:\n@mixin flex-dir($args...) { @include flex-direction($args...); }\n\n//----------------------------------------------------------------------\n\n// Flexbox Wrap\n//\n// The 'flex-wrap' property controls whether the flex container is single-line\n// or multi-line, and the direction of the cross-axis, which determines\n// the direction new lines are stacked in.\n//\n// Values: nowrap | wrap | wrap-reverse\n// Default: nowrap\n//\n// http://w3.org/tr/css3-flexbox/#flex-wrap-property\n\n@mixin flex-wrap($value: nowrap) {\n // No Webkit Box fallback.\n -webkit-flex-wrap: $value;\n -moz-flex-wrap: $value;\n @if $value == nowrap {\n -ms-flex-wrap: none;\n } @else {\n -ms-flex-wrap: $value;\n }\n flex-wrap: $value;\n}\n\n//----------------------------------------------------------------------\n\n// Flexbox Flow (shorthand)\n//\n// The 'flex-flow' property is a shorthand for setting the 'flex-direction'\n// and 'flex-wrap' properties, which together define the flex container's\n// main and cross axes.\n//\n// Values: | \n// Default: row nowrap\n//\n// http://w3.org/tr/css3-flexbox/#flex-flow-property\n\n@mixin flex-flow($values: (row nowrap)) {\n // No Webkit Box fallback.\n -webkit-flex-flow: $values;\n -moz-flex-flow: $values;\n -ms-flex-flow: $values;\n flex-flow: $values;\n}\n\n//----------------------------------------------------------------------\n\n// Flexbox Order\n//\n// The 'order' property controls the order in which flex items appear within\n// their flex container, by assigning them to ordinal groups.\n//\n// Default: 0\n//\n// http://w3.org/tr/css3-flexbox/#order-property\n\n@mixin order($int: 0) {\n -ms-flex-order: $int;\n -webkit-order: $int;\n -moz-order: $int;\n order: $int;\n -webkit-box-ordinal-group: $int + 1;\n}\n\n//----------------------------------------------------------------------\n\n// Flexbox Grow\n//\n// The 'flex-grow' property sets the flex grow factor. Negative numbers\n// are invalid.\n//\n// Default: 0\n//\n// http://w3.org/tr/css3-flexbox/#flex-grow-property\n\n@mixin flex-grow($int: 0) {\n -webkit-box-flex: $int;\n -webkit-flex-grow: $int;\n -moz-flex-grow: $int;\n -ms-flex-positive: $int;\n flex-grow: $int;\n}\n\n//----------------------------------------------------------------------\n\n// Flexbox Shrink\n//\n// The 'flex-shrink' property sets the flex shrink factor. Negative numbers\n// are invalid.\n//\n// Default: 1\n//\n// http://w3.org/tr/css3-flexbox/#flex-shrink-property\n\n@mixin flex-shrink($int: 1) {\n -webkit-flex-shrink: $int;\n -moz-flex-shrink: $int;\n -ms-flex-negative: $int;\n flex-shrink: $int;\n}\n\n//----------------------------------------------------------------------\n\n// Flexbox Basis\n//\n// The 'flex-basis' property sets the flex basis. Negative lengths are invalid.\n//\n// Values: Like \"width\"\n// Default: auto\n//\n// http://www.w3.org/TR/css3-flexbox/#flex-basis-property\n\n@mixin flex-basis($value: auto) {\n -webkit-flex-basis: $value;\n -moz-flex-basis: $value;\n -ms-flex-preferred-size: $value;\n flex-basis: $value;\n}\n\n//----------------------------------------------------------------------\n\n// Flexbox \"Flex\" (shorthand)\n//\n// The 'flex' property specifies the components of a flexible length: the\n// flex grow factor and flex shrink factor, and the flex basis. When an\n// element is a flex item, 'flex' is consulted instead of the main size\n// property to determine the main size of the element. If an element is\n// not a flex item, 'flex' has no effect.\n//\n// Values: none | || \n// Default: See individual properties (1 1 0).\n//\n// http://w3.org/tr/css3-flexbox/#flex-property\n\n@mixin flex($fg: 1, $fs: null, $fb: null) {\n\n // Set a variable to be used by box-flex properties\n $fg-boxflex: $fg;\n\n // Box-Flex only supports a flex-grow value so let's grab the\n // first item in the list and just return that.\n @if type-of($fg) == \"list\" {\n $fg-boxflex: nth($fg, 1);\n }\n\n -webkit-box-flex: $fg-boxflex;\n -webkit-flex: $fg $fs $fb;\n -moz-box-flex: $fg-boxflex;\n -moz-flex: $fg $fs $fb;\n -ms-flex: $fg $fs $fb;\n flex: $fg $fs $fb;\n}\n\n//----------------------------------------------------------------------\n\n// Flexbox Justify Content\n//\n// The 'justify-content' property aligns flex items along the main axis\n// of the current line of the flex container. This is done after any flexible\n// lengths and any auto margins have been resolved. Typically it helps distribute\n// extra free space leftover when either all the flex items on a line are\n// inflexible, or are flexible but have reached their maximum size. It also\n// exerts some control over the alignment of items when they overflow the line.\n//\n// Note: 'space-*' values not supported in older syntaxes.\n//\n// Values: flex-start | flex-end | center | space-between | space-around\n// Default: flex-start\n//\n// http://w3.org/tr/css3-flexbox/#justify-content-property\n\n@mixin justify-content($value: flex-start) {\n @if $value == flex-start {\n -webkit-box-pack: start;\n -ms-flex-pack: start;\n } @else if $value == flex-end {\n -webkit-box-pack: end;\n -ms-flex-pack: end;\n } @else if $value == space-between {\n -webkit-box-pack: justify;\n -ms-flex-pack: justify;\n } @else if $value == space-around {\n -ms-flex-pack: distribute;\n } @else {\n -webkit-box-pack: $value;\n -ms-flex-pack: $value;\n }\n -webkit-justify-content: $value;\n -moz-justify-content: $value;\n justify-content: $value;\n}\n// Shorter version:\n@mixin flex-just($args...) { @include justify-content($args...); }\n\n//----------------------------------------------------------------------\n\n// Flexbox Align Items\n//\n// Flex items can be aligned in the cross axis of the current line of the\n// flex container, similar to 'justify-content' but in the perpendicular\n// direction. 'align-items' sets the default alignment for all of the flex\n// container's items, including anonymous flex items. 'align-self' allows\n// this default alignment to be overridden for individual flex items. (For\n// anonymous flex items, 'align-self' always matches the value of 'align-items'\n// on their associated flex container.)\n//\n// Values: flex-start | flex-end | center | baseline | stretch\n// Default: stretch\n//\n// http://w3.org/tr/css3-flexbox/#align-items-property\n\n@mixin align-items($value: stretch) {\n @if $value == flex-start {\n -webkit-box-align: start;\n -ms-flex-align: start;\n } @else if $value == flex-end {\n -webkit-box-align: end;\n -ms-flex-align: end;\n } @else {\n -webkit-box-align: $value;\n -ms-flex-align: $value;\n }\n -webkit-align-items: $value;\n -moz-align-items: $value;\n align-items: $value;\n}\n\n//----------------------------------\n\n// Flexbox Align Self\n//\n// Values: auto | flex-start | flex-end | center | baseline | stretch\n// Default: auto\n\n@mixin align-self($value: auto) {\n // No Webkit Box Fallback.\n -webkit-align-self: $value;\n -moz-align-self: $value;\n @if $value == flex-start {\n -ms-flex-item-align: start;\n } @else if $value == flex-end {\n -ms-flex-item-align: end;\n } @else {\n -ms-flex-item-align: $value;\n }\n align-self: $value;\n}\n\n//----------------------------------------------------------------------\n\n// Flexbox Align Content\n//\n// The 'align-content' property aligns a flex container's lines within the\n// flex container when there is extra space in the cross-axis, similar to\n// how 'justify-content' aligns individual items within the main-axis. Note,\n// this property has no effect when the flexbox has only a single line.\n//\n// Values: flex-start | flex-end | center | space-between | space-around | stretch\n// Default: stretch\n//\n// http://w3.org/tr/css3-flexbox/#align-content-property\n\n@mixin align-content($value: stretch) {\n // No Webkit Box Fallback.\n -webkit-align-content: $value;\n -moz-align-content: $value;\n @if $value == flex-start {\n -ms-flex-line-pack: start;\n } @else if $value == flex-end {\n -ms-flex-line-pack: end;\n } @else {\n -ms-flex-line-pack: $value;\n }\n align-content: $value;\n}\n","@mixin block-elements {\n h1,\n h2,\n h3,\n h4,\n h5,\n h6,\n p,\n hr,\n blockquote,\n figure,\n pre,\n .highlighter-rouge,\n ul,\n ol,\n dl,\n table,\n .footnotes {\n @content;\n }\n}\n@mixin heading-elements {\n h1, h2, h3, h4, h5, h6 {\n @content;\n }\n}\n\n*,\n::before,\n::after {\n box-sizing: border-box;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\n/**\n * 1. Prevent adjustments of font size after orientation changes in iOS.\n **/\n\nhtml {\n font-size: map-get($base, font-size-root);\n -webkit-text-size-adjust: 100%; /* 1 */\n @media print {\n font-size: map-get($base, font-size-root-sm);\n }\n}\n\nbody {\n padding: 0;\n margin: 0;\n font: map-get($base, font-weight) #{map-get($base, font-size)}/#{map-get($base, line-height)} map-get($base, font-family);\n ::-moz-selection {\n background: $select-color;\n }\n ::-webkit-selection {\n background: $select-color;\n }\n ::selection {\n background: $select-color;\n }\n}\n\n@include block-elements() {\n padding: 0;\n margin: map-get($spacers, 2) 0;\n}\n\ninput, textarea, select, button {\n font: map-get($base, font-weight) #{map-get($base, font-size)}/#{map-get($base, line-height)} map-get($base, font-family);\n color: $text-color;\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\nstrong {\n font-weight: map-get($base, font-weight-bold);\n}\n\nh1 {\n font-size: map-get($base, font-size-h1);\n color: $text-color-d;\n @include media-breakpoint-down(md) {\n font-size: map-get($base, font-size-h1-sm);\n }\n}\n\nh2 {\n font-size: map-get($base, font-size-h2);\n color: $text-color-d;\n @include media-breakpoint-down(md) {\n font-size: map-get($base, font-size-h2-sm);\n }\n}\n\nh3 {\n font-size: map-get($base, font-size-h3);\n color: $text-color-d;\n @include media-breakpoint-down(md) {\n font-size: map-get($base, font-size-h3-sm);\n }\n}\n\nh4 {\n font-size: map-get($base, font-size-h4);\n color: $text-color;\n @include media-breakpoint-down(md) {\n font-size: map-get($base, font-size-h4-sm);\n }\n}\n\nh5 {\n font-size: map-get($base, font-size-h5);\n color: $text-color;\n @include media-breakpoint-down(md) {\n font-size: map-get($base, font-size-h5-sm);\n }\n}\n\nh6 {\n font-size: map-get($base, font-size-h6);\n color: $text-color-l;\n @include media-breakpoint-down(md) {\n font-size: map-get($base, font-size-h6-sm);\n }\n}\n\na {\n font-weight: map-get($base, font-weight-bold);\n @include link-colors($main-color-1);\n}\n\npre, code {\n font-family: map-get($base, font-family-code);\n}\n\ncode {\n font-size: map-get($base, font-size-xs);\n line-height: map-get($base, line-height-sm);\n}\n\nfigure > img {\n display: block;\n}\n\nfigcaption {\n font-size: map-get($base, font-size-sm);\n}\n\nbutton {\n padding: 0;\n margin: 0;\n font-size: map-get($spacers, 3);\n cursor: pointer;\n background-color: transparent;\n border-width: 0;\n outline: none;\n}\n\ninput {\n &::-ms-clear {\n display: none;\n }\n &:focus {\n outline: none;\n }\n}\n\n// mermaid\n.mermaidTooltip {\n display: none;\n}\n","@media print {\n a {\n @include plain() {\n text-decoration: underline;\n }\n @include hover() {\n text-decoration: underline;\n }\n @include active() {\n text-decoration: underline;\n }\n }\n\n img,\n tr,\n pre,\n blockquote {\n page-break-inside: avoid;\n }\n}\n","@mixin button() {\n display: inline-block;\n font-weight: map-get($button, font-weight);\n line-height: 1 !important;\n text-decoration: none !important;\n cursor: pointer;\n outline: none;\n @include user-select(none);\n svg {\n width: 1rem;\n height: 1rem;\n }\n @include disabled() {\n cursor: not-allowed;\n }\n}\n\n.button {\n @include button();\n}\n\n.button--primary {\n @include clickable($text-color-1, $main-color-1);\n}\n\n.button--secondary {\n @include clickable($text-color-3, $main-color-3);\n}\n\n.button--success {\n @include clickable($text-color-function, $green);\n}\n\n.button--info {\n @include clickable($text-color-function, $blue);\n}\n\n.button--warning {\n @include clickable($text-color-function, $yellow);\n}\n\n.button--error {\n @include clickable($text-color-function, $red);\n}\n\n.button--theme-light {\n @include clickable($text-color-theme-dark, $main-color-theme-light);\n}\n\n.button--theme-dark {\n @include clickable($text-color-theme-light, $main-color-theme-dark);\n}\n\n.button--outline-primary {\n color: $main-color-1;\n border: 1px solid $main-color-1;\n @include clickable($main-color-1, transparent, $text-color-1, $main-color-1);\n}\n\n.button--outline-secondary {\n color: $main-color-3;\n border: 1px solid $main-color-3;\n @include clickable($main-color-3, transparent, $text-color-3, $main-color-3);\n}\n\n.button--outline-success {\n color: $green;\n border: 1px solid $green;\n @include clickable($green, transparent, $text-color-function, $green);\n}\n\n.button--outline-info {\n color: $blue;\n border: 1px solid $blue;\n @include clickable($blue, transparent, $text-color-function, $blue);\n}\n\n.button--outline-warning {\n color: $yellow;\n border: 1px solid $yellow;\n @include clickable($yellow, transparent, $text-color-function, $yellow);\n}\n\n.button--outline-error {\n color: $red;\n border: 1px solid $red;\n @include clickable($red, transparent, $text-color-function, $red);\n}\n\n.button--outline-theme-light {\n color: $main-color-theme-light;\n border: 1px solid $main-color-theme-light;\n @include clickable($main-color-theme-light, transparent, $text-color-theme-dark, $main-color-theme-light);\n}\n\n.button--outline-theme-dark {\n color: $main-color-theme-dark;\n border: 1px solid $main-color-theme-dark;\n @include clickable($main-color-theme-dark, transparent, $text-color-theme-light, $main-color-theme-dark);\n}\n\n.button--pill {\n border-radius: map-get($button, pill-radius);\n @extend .button--md;\n}\n\n.button--rounded {\n border-radius: map-get($base, border-radius);\n @extend .button--md;\n}\n\n.button--circle {\n @include inline-flex();\n @include justify-content(center);\n @include align-items(center);\n border-radius: 50%;\n @extend .button--md;\n}\n\n.button--md {\n padding: map-get($button, padding-y) map-get($button, padding-x);\n font-size: map-get($base, font-size);\n &.button--circle {\n width: map-get($button, circle-diameter);\n height: map-get($button, circle-diameter);\n }\n}\n\n.button--xs {\n padding: map-get($button, padding-y-xs) map-get($button, padding-x-xs);\n font-size: map-get($base, font-size-xs);\n &.button--circle {\n width: map-get($button, circle-diameter-xs);\n height: map-get($button, circle-diameter-xs);\n }\n}\n\n.button--sm {\n padding: map-get($button, padding-y-sm) map-get($button, padding-x-sm);\n font-size: map-get($base, font-size-sm);\n &.button--circle {\n width: map-get($button, circle-diameter-sm);\n height: map-get($button, circle-diameter-sm);\n }\n}\n\n.button--lg {\n padding: map-get($button, padding-y-lg) map-get($button, padding-x-lg);\n font-size: map-get($base, font-size-lg);\n &.button--circle {\n width: map-get($button, circle-diameter-lg);\n height: map-get($button, circle-diameter-lg);\n }\n}\n\n.button--xl {\n padding: map-get($button, padding-y-xl) map-get($button, padding-x-xl);\n font-size: map-get($base, font-size-xl);\n &.button--circle {\n width: map-get($button, circle-diameter-xl);\n height: map-get($button, circle-diameter-xl);\n }\n}\n","@mixin user-select($value) {\n -webkit-user-select: $value;\n -moz-user-select: $value;\n -ms-user-select: $value;\n user-select: $value;\n}\n",".image {\n max-width: 100%;\n @extend .image--md;\n}\n.image--md {\n width: map-get($image, width);\n}\n.image--xl {\n width: map-get($image, width-xl);\n}\n.image--lg {\n width: map-get($image, width-lg);\n}\n.image--sm {\n width: map-get($image, width-sm);\n}\n.image--xs {\n width: map-get($image, width-xs);\n}\n","\n.card {\n max-width: 18rem;\n border-radius: map-get($base, border-radius);\n @include box-shadow();\n @include transition(box-shadow map-get($animation, duration) map-get($animation, timing-function));\n & > :first-child {\n border-top-left-radius: map-get($base, border-radius);\n border-top-right-radius: map-get($base, border-radius);\n }\n & > :last-child {\n border-bottom-right-radius: map-get($base, border-radius);\n border-bottom-left-radius: map-get($base, border-radius);\n }\n}\n\n.cell {\n & > .card {\n max-width: unset;\n }\n}\n\n.card__content {\n padding: map-get($spacers, 2) map-get($spacers, 3);\n}\n\n.card__header, .card__header > a {\n @include link-colors($text-color-d, $main-color-1);\n}\n\n.card__image {\n position: relative;\n width: 100%;\n & > img {\n display: block;\n width: 100%;\n height: auto;\n border-radius: inherit;\n }\n & > .overlay {\n position: absolute;\n width: 100%;\n max-height: 100%;\n padding: map-get($spacers, 2);\n a {\n text-decoration: none !important;\n }\n }\n & > .overlay, & > .overlay--top {\n top: 0;\n bottom: auto;\n border-top-left-radius: inherit;\n border-top-right-radius: inherit;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n }\n & > .overlay--bottom {\n top: auto;\n bottom: 0;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n border-bottom-right-radius: inherit;\n border-bottom-left-radius: inherit;\n }\n & > .overlay--full {\n top: 0;\n bottom: 0;\n }\n & > .overlay, & > .overlay--dark {\n @extend .text--dark;\n background-color: rgba(#000, .4);\n }\n & > .overlay--light {\n @extend .text--light;\n background: rgba(#fff, .4);\n }\n}\n\n.card--clickable {\n cursor: pointer;\n @include hover() {\n @include box-shadow(2);\n .card__image {\n & > img {\n height: inherit;\n }\n }\n }\n @include transition(map-get($clickable, transition));\n}\n\n.card--flat {\n @include box-shadow(0);\n .card__image {\n & > img {\n border-radius: map-get($base, border-radius);\n }\n }\n .card__content {\n padding-top: 0;\n padding-left: 0;\n }\n}\n",".gallery {\n height: 100%;\n @include flexbox();\n @include flex-direction(column);\n}\n\n.gallery__swiper {\n @include flex(1);\n}\n\n.gallery-item {\n @include flexbox();\n @include align-items(center);\n @include justify-content(center);\n height: 100%;\n overflow: hidden;\n}\n\n.gallery-item__main {\n display: block;\n}\n",".hero {\n background-position: 50% 50%;\n @include flexbox();\n @include flex-direction(column);\n @include justify-content(center);\n h1 { font-size: map-get($base, font-size-h1-xl); }\n h2 { font-size: map-get($base, font-size-h2-xl); }\n h3 { font-size: map-get($base, font-size-h3-xl); }\n h4 { font-size: map-get($base, font-size-h4-xl); }\n h5 { font-size: map-get($base, font-size-h5-xl); }\n h6 { font-size: map-get($base, font-size-h6-xl); }\n p { font-size: map-get($base, font-size-xl); }\n @include media-breakpoint-down(lg) {\n h1 { font-size: map-get($base, font-size-h1-lg); }\n h2 { font-size: map-get($base, font-size-h2-lg); }\n h3 { font-size: map-get($base, font-size-h3-lg); }\n h4 { font-size: map-get($base, font-size-h4-lg); }\n h5 { font-size: map-get($base, font-size-h5-lg); }\n h6 { font-size: map-get($base, font-size-h6-lg); }\n p { font-size: map-get($base, font-size-lg); }\n }\n @include media-breakpoint-down(md) {\n h1 { font-size: map-get($base, font-size-h1-sm); }\n h2 { font-size: map-get($base, font-size-h2-sm); }\n h3 { font-size: map-get($base, font-size-h3-sm); }\n h4 { font-size: map-get($base, font-size-h4-sm); }\n h5 { font-size: map-get($base, font-size-h5-sm); }\n h6 { font-size: map-get($base, font-size-h6-sm); }\n p { font-size: map-get($base, font-size); }\n }\n background-size: cover;\n\n}\n\n.hero--center {\n text-align: center;\n .menu {\n @extend .menu--center;\n }\n}\n\n.hero--light {\n @extend .text--light;\n}\n\n.hero--dark {\n @extend .text--dark;\n}\n\n.hero__content {\n margin: map-get($spacers, 5);\n @include media-breakpoint-down(lg) {\n margin: map-get($spacers, 5) map-get($spacers, 4);\n }\n @include media-breakpoint-down(md) {\n margin: map-get($spacers, 4) map-get($spacers, 3);\n }\n}\n\n.heros {\n & > .hero {\n margin: map-get($spacers, 5);\n @include media-breakpoint-down(lg) {\n margin: map-get($spacers, 3);\n }\n @include media-breakpoint-down(md) {\n margin: map-get($spacers, 2) 0;\n }\n }\n}\n","@mixin menu-direction($direction: default) {\n @if $direction == default {\n $direction: \"horizontal\";\n }\n @if $direction == \"vertical\" {\n @include flex-direction(column);\n } @else {\n @include flex-direction(row);\n }\n}\n\n@mixin menu($horizontal-spacer: default, $horizontal-item-vertical-spacer: default, $wrap: default) {\n @if $horizontal-spacer == default {\n $horizontal-spacer: map-get($menu, horizontal-spacer);\n }\n @if $horizontal-item-vertical-spacer == default {\n $horizontal-item-vertical-spacer: map-get($menu, horizontal-item-vertical-spacer);\n }\n @if $wrap == default {\n $wrap: wrap;\n }\n @include flexbox();\n @include flex-wrap($wrap);\n margin-top: 0;\n margin-bottom: 0;\n & > li {\n @if $horizontal-item-vertical-spacer {\n margin-top: map-get($spacers, $horizontal-item-vertical-spacer);\n margin-bottom: map-get($spacers, $horizontal-item-vertical-spacer);\n }\n margin-right: map-get($spacers, $horizontal-spacer);\n list-style-type: none;\n &:last-child {\n margin-right: 0;\n }\n }\n}\n\n.menu {\n @include menu();\n @include menu-direction();\n @include align-items(center);\n}\n\n.menu--vertical {\n @include menu-direction(\"vertical\");\n @include align-items(normal);\n & > li {\n margin-right: 0;\n }\n}\n\n.menu--inline {\n @include inline-flex();\n}\n\n.menu--center {\n @include justify-content(center);\n}\n\n.menu--nowrap {\n @include flex-wrap(nowrap);\n}\n\n.menu--grow {\n @include flex-grow(1);\n}\n","@mixin modal($z-index: default, $color: default, $background-color: default) {\n @if $z-index == default {\n $z-index: map-get($z-indexes, modal);\n }\n @if $color == default {\n $color: $text-color-theme-dark;\n }\n @if $background-color == default {\n $background-color: $mask-color;\n }\n position: fixed;\n top: 0;\n left: 0;\n z-index: $z-index;\n width: 100%;\n height: 100%;\n color: $color;\n touch-action: none;\n background-color: $background-color;\n opacity: 0;\n @include transform(translate(100%, 0));\n @include transition(#{opacity map-get($animation, duration) map-get($animation, timing-function),\n transform 0s map-get($animation, duration) map-get($animation, timing-function)});\n}\n@mixin modal--show() {\n opacity: 1;\n @include transform(translate(0, 0));\n @include transition(#{opacity map-get($animation, duration) map-get($animation, timing-function)});\n}\n\n.modal {\n @include modal();\n}\n.modal--show {\n @include modal--show();\n}\n.modal--overflow {\n @include overflow(auto);\n}\n","@mixin transform($value) {\n -webkit-transform: $value;\n transform: $value;\n}\n","ul.toc {\n display: block;\n margin: 0;\n color: $text-color;\n list-style-type: none;\n & > li {\n margin: map-get($spacers, 1) / 2 0;\n a {\n display: inline-block;\n margin: map-get($spacers, 1) / 4 0;\n text-decoration: none !important;\n }\n }\n .toc-h1,\n .toc-h2,\n .toc-h3,\n .toc-h4,\n .toc-h5,\n .toc-h6 {\n a {\n @include link-colors($text-color, $main-color-1);\n }\n &.active {\n a {\n @include link-colors($main-color-1);\n }\n }\n }\n\n .toc-h2,\n .toc-h3,\n .toc-h4,\n .toc-h5,\n .toc-h6 {\n &, a {\n font-size: map-get($base, font-size-xs);\n font-weight: map-get($base, font-weight);\n line-height: map-get($base, line-height-xs);\n }\n }\n .toc-h1 {\n @include split-line(bottom);\n padding: map-get($spacers, 2) 0 map-get($spacers, 1) 0;\n margin-bottom: map-get($spacers, 2);\n color: $text-color-d;\n &, a {\n font-size: map-get($base, font-size-sm);\n font-weight: map-get($base, font-weight-bold);\n line-height: map-get($base, line-height-sm);\n }\n a {\n @include link-colors($text-color-d, $main-color-1);\n }\n }\n .toc-h2 {\n &, a {\n font-weight: map-get($base, font-weight-bold);\n }\n }\n .toc-h3 {\n margin-left: map-get($spacers, 3);\n }\n .toc-h4 {\n margin-left: map-get($spacers, 3) * 2;\n }\n .toc-h5,\n .toc-h6 {\n margin-left: map-get($spacers, 3) * 3;\n }\n .toc-h6 {\n color: $text-color-l;\n a {\n @include link-colors($text-color-l, $main-color-1);\n }\n }\n}\n\nul.toc--ellipsis {\n & > li {\n @include overflow(hidden);\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n}\n\nul.toc--navigator {\n & > li {\n a {\n padding-left: map-get($spacers, 2);\n margin: map-get($spacers, 1) 0;\n }\n &.active {\n a {\n margin-left: -4px;\n @include split-line(left, 4px, $main-color-1);\n }\n }\n }\n .toc-h2,\n .toc-h3,\n .toc-h4 {\n color: $text-color-l;\n a {\n @include link-colors($text-color-l);\n }\n }\n .toc-h1 {\n &, a {\n font-size: map-get($base, font-size);\n line-height: map-get($base, line-height);\n }\n color: $text-color;\n a {\n @include link-colors($text-color-d);\n }\n }\n .toc-h2 {\n &, a {\n font-size: map-get($base, font-size-sm);\n font-weight: map-get($base, font-weight-bold);\n line-height: map-get($base, line-height-sm);\n }\n }\n}\n","@mixin split-line($direction: default, $width: default, $color: default) {\n @if $direction == default {\n $direction: top;\n }\n @if $color == default {\n $color: $border-color-l;\n }\n @if $width == default {\n $width: 1px;\n }\n border: 0 solid $color;\n @if $direction == top {\n border-top-width: $width;\n }\n @if $direction == right {\n border-right-width: $width;\n }\n @if $direction == bottom {\n border-bottom-width: $width;\n }\n @if $direction == left {\n border-left-width: $width;\n }\n}\n",".item {\n @include flexbox();\n @include media-breakpoint-down(md) {\n @include flex-direction(column);\n }\n}\n\n.item__image {\n margin-right: map-get($spacers, 3);\n & + .item__content {\n & > :first-child {\n margin-top: 0;\n & > :first-child {\n margin-top: 0;\n }\n }\n }\n @include media-breakpoint-down(md) {\n margin-right: 0;\n }\n}\n\n.item__content {\n @include flex(1);\n min-width: 0;\n}\n\na > .item__header, a.item__header, .item__header > a {\n @include link-colors($text-color-d, $main-color-1);\n}\n\n.item__meta {\n color: $text-color-l;\n}\n\n.item__description {\n &, .article__content {\n font-size: map-get($base, font-size-sm);\n line-height: map-get($base, line-height);\n @include block-elements() {\n margin-top: map-get($spacers, 2);\n margin-bottom: map-get($spacers, 2);\n }\n @include heading-elements() {\n margin-top: map-get($spacers, 3);\n }\n h1, h2, h3 {\n color: $text-color;\n }\n h1, h2 {\n padding: 0;\n border: none;\n }\n h1 {\n font-size: map-get($base, font-size-h1-xs);\n }\n h2 {\n font-size: map-get($base, font-size-h2-xs);\n }\n h3 {\n font-size: map-get($base, font-size-h3-xs);\n }\n h4 {\n font-size: map-get($base, font-size-h4-xs);\n }\n h5 {\n font-size: map-get($base, font-size-h5-xs);\n }\n h6 {\n font-size: map-get($base, font-size-h6-xs);\n }\n img {\n max-height: 32rem;\n @include media-breakpoint-down(md) {\n max-height: 14rem;\n }\n }\n }\n}\n\n.items {\n & > .item {\n &:not(:last-child) {\n margin-bottom: map-get($spacers, 2);\n }\n }\n}\n\n.items--divided {\n & > .item {\n &:not(:first-child) {\n padding-top: map-get($spacers, 4);\n }\n &:not(:last-child) {\n padding-bottom: map-get($spacers, 4);\n @include split-line(bottom);\n }\n list-style-type: none;\n }\n}\n",".swiper {\n position: relative;\n @include overflow(hidden);\n}\n\n.swiper__wrapper, .swiper__slide {\n width: 100%;\n height: 100%;\n}\n\n.swiper__wrapper {\n @include flexbox();\n}\n\n.swiper__wrapper--animation {\n @include transition(transform map-get($animation, duration) map-get($animation, timing-function));\n}\n\n.swiper__slide {\n @include flex-shrink(0);\n & > img {\n max-width: 100%;\n }\n}\n\n.swiper__button {\n position: absolute;\n top: 50%;\n @extend .button, .button--circle;\n @include transform(translate(0, -50%));\n @include clickable($text-color-d, rgba($main-color-3, .4));\n}\n\n.swiper--light .swiper__button {\n @include clickable($text-color-theme-light, rgba($main-color-theme-dark, .4));\n}\n\n.swiper--dark .swiper__button {\n @include clickable($text-color-theme-dark, rgba($main-color-theme-light, .4));\n}\n\n.swiper__button--prev {\n left: 10px;\n}\n\n.swiper__button--next {\n right: 10px;\n}\n","@mixin animation($value) {\n -webkit-animation: $value;\n animation: $value;\n}\n\n@mixin keyframes($name) {\n @-webkit-keyframes #{$name} {\n @content;\n }\n @keyframes #{$name} {\n @content;\n }\n}\n","@include keyframes(fade-in) {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n","@include keyframes(fade-in-down) {\n from {\n opacity: 0;\n @include transform(translateY(-2rem));\n }\n to {\n opacity: 1;\n @include transform(translateY(0));\n }\n}\n","@include keyframes(fade-in-up) {\n from {\n opacity: 0;\n @include transform(translateY(2rem));\n }\n to {\n opacity: 1;\n @include transform(translateY(0));\n }\n}\n",".main {\n width: 100%;\n max-width: map-get($layout, content-max-width);\n padding: 0 map-get($spacers, 5);\n margin: 0 auto;\n @include media-breakpoint-down(lg) {\n padding: 0 map-get($spacers, 4);\n }\n @include media-breakpoint-down(md) {\n padding: 0 map-get($spacers, 3);\n }\n}\n\n.has-aside {\n .main {\n max-width: map-get($layout, content-max-width) + map-get($layout, aside-width);\n @include media-breakpoint-down(lg) {\n max-width: map-get($layout, content-max-width);\n }\n }\n}\n\n.full-width {\n .main {\n width: 100%;\n max-width: 100%;\n }\n}\n",".header {\n background: $header-background;\n a {\n font-weight: map-get($base, font-weight);\n text-decoration: none !important;\n @include link-colors($header-text-color, $main-color-1);\n }\n .main {\n @include flexbox();\n @include media-breakpoint-down(md) {\n @include flex-direction(column);\n }\n }\n}\n\n.header--dark {\n @extend .text--dark;\n background: rgba(#000, .15);\n .navigation__item--active {\n &::after {\n @include split-line(bottom, 4px, $text-color-theme-dark);\n }\n }\n}\n\n.header--light {\n @extend .text--light;\n background: rgba(#fff, .15);\n .navigation__item--active {\n &::after {\n @include split-line(bottom, 4px, $text-color-theme-light);\n }\n }\n}\n\n.header__title {\n @include menu(3, 0);\n @include align-items(center);\n @include flex-wrap(nowrap);\n @include flex(1);\n height: map-get($layout, header-height);\n margin-right: map-get($spacers, 3);\n white-space: nowrap;\n @include media-breakpoint-down(md) {\n height: auto;\n margin-right: 0;\n }\n & > .header__brand {\n @include flex(1);\n @include media-breakpoint-down(md) {\n height: map-get($layout, header-height-sm);\n }\n }\n & > .search-button {\n display: none;\n margin-left: map-get($spacers, 2);\n @include media-breakpoint-down(md) {\n @include flexbox();\n }\n }\n}\n\n.header__brand {\n @include flexbox();\n @include align-items(center);\n & > svg {\n width: map-get($base, font-size-h4) * 1.6;\n height: map-get($base, font-size-h4) * 1.6;\n margin-right: map-get($spacers, 3);\n vertical-align: middle;\n @include media-breakpoint-down(md) {\n width: map-get($base, font-size-h4) * 1.2;\n height: map-get($base, font-size-h4) * 1.2;\n }\n }\n & > a {\n display: inline-block;\n font-size: map-get($base, font-size-h4);\n @include media-breakpoint-down(md) {\n font-size: map-get($base, font-size-h4-small);\n }\n }\n}\n\n.navigation {\n @include overflow(auto, \"x\");\n & > ul {\n height: map-get($layout, header-height);\n padding-bottom: 0;\n margin: 0;\n @include media-breakpoint-down(md) {\n padding-bottom: 4px;\n margin: -4px 0 0 0;\n }\n @include menu(3, 2, nowrap);\n @include align-items(center);\n @include media-breakpoint-down(md) {\n height: auto;\n }\n .search-button {\n @include media-breakpoint-down(md) {\n display: none;\n }\n }\n }\n}\n\n.navigation__item {\n &::after {\n display: block;\n margin-bottom: -4px;\n content: \"\";\n @include split-line(bottom, 4px, transparent);\n }\n}\n.navigation__item--active {\n a {\n @include link-colors($main-color-1, $main-color-1);\n }\n &::after {\n @include split-line(bottom, 4px, $main-color-1);\n }\n}\n","/**\n * Site Info\n */\n\n.footer {\n @include flexbox();\n @include align-items(center);\n color: $footer-text-color;\n background: $footer-background;\n a {\n @include link-colors ($footer-text-color, $main-color-1);\n }\n .site-info {\n font-size: map-get($base, font-size-xs);\n text-align: center;\n .menu {\n line-height: map-get($base, line-height-xs);\n & > * {\n &:not(:last-child) {\n @include split-line(right, default, $footer-text-color);\n padding-right: map-get($spacers, 1);\n margin-right: map-get($spacers, 1);\n }\n }\n }\n }\n}\n.footer__author-links {\n @include overflow(auto);\n .author-links {\n text-align: center;\n }\n}\n",".article-list {\n .item__meta {\n padding: 0 map-get($spacers, 3) 0 0;\n font-family: map-get($base, font-family-code);\n font-size: map-get($base, font-size-sm);\n white-space: nowrap;\n }\n &.grid--sm {\n .card__header {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n }\n}\n.article-list__group-header {\n margin-top: map-get($spacers, 3);\n}\n",".article__info {\n font-size: map-get($base, font-size-sm);\n color: $text-color-l;\n .left-col {\n float: left;\n @include media-breakpoint-down(md) {\n float: none;\n }\n }\n .right-col {\n float: right;\n margin-left: map-get($button, padding-x-sm);\n @include media-breakpoint-down(md) {\n float: none;\n }\n & > li {\n &:not(:last-child) {\n @include split-line(right, default, $text-color-l);\n padding-right: map-get($spacers, 2);\n margin-right: map-get($spacers, 2);\n line-height: map-get($base, line-height-xs);\n }\n }\n }\n}\n",".article__header {\n margin-top: map-get($spacers, 5);\n margin-bottom: map-get($spacers, 4);\n @include media-breakpoint-down(md) {\n margin-top: map-get($spacers, 4);\n }\n header, h1 {\n display: inline;\n }\n h1 {\n word-wrap: break-word;\n }\n .split-space {\n @include user-select(none);\n }\n .edit-on-github {\n text-decoration: none !important;\n }\n}\n\n.article__header--overlay {\n .overlay {\n min-height: 36rem;\n padding-top: map-get($spacers, 5) * 2;\n padding-bottom: map-get($spacers, 5) * 2;\n @include media-breakpoint-down(md) {\n min-height: 29rem;\n padding-top: map-get($spacers, 5);\n padding-bottom: map-get($spacers, 5);\n }\n }\n .overlay__excerpt {\n font-size: map-get($base, font-size-h3-xl);\n @include media-breakpoint-down(lg) {\n font-size: map-get($base, font-size-h3-lg);\n }\n @include media-breakpoint-down(md) {\n font-size: map-get($base, font-size-h3-sm);\n }\n font-weight: map-get($base, font-weight-bold);\n }\n\n .article__header {\n margin-top: 0;\n }\n}\n\n.article__header--cover {\n width: 100%;\n}\n",".article__content {\n line-height: map-get($base, line-height-lg);\n word-wrap: break-word;\n @media print {\n line-height: map-get($base, line-height);\n }\n @include block-elements() {\n margin: map-get($spacers, 3) 0;\n @media print {\n margin: map-get($spacers, 2) 0;\n }\n }\n @include heading-elements() {\n position: relative;\n margin-top: map-get($spacers, 4);\n @media print {\n margin-top: map-get($spacers, 3);\n }\n & > .anchor {\n @include link-colors($border-color, $main-color-1);\n margin-left: map-get($spacers, 1);\n text-decoration: none;\n visibility: hidden;\n opacity: 0;\n & > i {\n font-size: map-get($base, font-size-sm);\n }\n }\n @include hover() {\n & > .anchor {\n cursor: pointer;\n visibility: visible;\n opacity: 1;\n }\n }\n }\n h1,\n h2 {\n @include split-line(bottom);\n }\n hr {\n border: none;\n @include horizontal-rules();\n }\n blockquote {\n padding-left: map-get($spacers, 3);\n font-size: map-get($base, font-size-sm);\n color: $text-color-l;\n @include split-line(left, 4px, $border-color);\n p {\n margin: map-get($spacers, 2) 0;\n }\n & > :last-child {\n margin-bottom: 0;\n }\n }\n img:not(.emoji) {\n max-width: 100%;\n vertical-align: middle;\n }\n .emoji {\n display: inline-block;\n width: map-get($base, line-height-lg) * .7rem;\n height: map-get($base, line-height-lg) * .7rem;\n vertical-align: text-bottom;\n }\n .footnotes {\n @include split-line();\n margin-top: map-get($spacers, 5);\n @media print {\n margin-top: map-get($spacers, 2) * 2;\n }\n }\n code {\n padding: map-get($spacers, 1) map-get($spacers, 2);\n background-color: $text-background-color;\n border-radius: map-get($base, border-radius);\n span {\n padding: 0;\n margin: 0;\n }\n }\n pre {\n @include overflow(auto);\n & > code {\n padding: 0;\n word-wrap: normal;\n background-color: transparent;\n &.language-mermaid, &.language-chart {\n svg {\n width: 100%;\n }\n display: none;\n &[data-processed] {\n display: block;\n }\n }\n }\n }\n .highlighter-rouge > .highlight, figure.highlight {\n & > pre {\n padding: map-get($spacers, 3) 0 map-get($spacers, 3) map-get($spacers, 3);\n margin: 0;\n background-color: $text-background-color;\n border-radius: map-get($base, border-radius);\n & > code {\n display: block;\n }\n }\n }\n figure.highlight {\n &::before {\n display: block;\n padding: map-get($spacers, 2) map-get($spacers, 3) map-get($spacers, 2) 0;\n font-weight: map-get($base, font-weight-bold);\n color: $decorate-color;\n text-align: right;\n text-transform: uppercase;\n content: attr(data-lang);\n background-color: $text-background-color;\n border-top-left-radius: map-get($base, border-radius);\n border-top-right-radius: map-get($base, border-radius);\n }\n & > pre {\n padding-top: 0;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n & > code {\n & > .rouge-table {\n width: auto;\n margin: 0 0 #{- map-get($spacers, 3)} #{- map-get($spacers, 3)};\n tbody, tr, td {\n padding-top: 0;\n padding-bottom: 0;\n border: none;\n }\n & > tbody {\n @include flexbox;\n & > tr {\n width: 100%;\n @include flexbox;\n & > .code {\n padding: 0 0 map-get($spacers, 3) map-get($spacers, 2);\n @include overflow(auto);\n }\n }\n }\n tbody td {\n &.gl {\n padding-left: map-get($spacers, 3);\n }\n & > pre {\n display: block;\n margin: 0;\n border-radius: 0;\n @include overflow(auto);\n &.lineno {\n color: $text-color-l;\n @include user-select(none);\n }\n }\n }\n }\n }\n }\n }\n ul, ol {\n margin-left: map-get($spacers, 4);\n ul, ol {\n margin-top: 0;\n margin-bottom: 0;\n }\n li {\n p {\n margin: map-get($spacers, 2);\n @media print {\n margin: map-get($spacers, 1);\n }\n }\n }\n }\n dl {\n dt, dd {\n p {\n margin: map-get($spacers, 2);\n @media print {\n margin: map-get($spacers, 1);\n }\n }\n }\n dt {\n font-weight: map-get($base, font-weight-bold);\n }\n dd {\n margin-left: 2rem;\n }\n }\n ul.task-list {\n margin-left: 0;\n list-style-type: none;\n ul, ol {\n margin-left: map-get($spacers, 4);\n }\n }\n table {\n display: block;\n width: 100%;\n border-collapse: collapse;\n @include overflow(auto);\n thead, tfoot {\n background-color: $text-background-color;\n }\n th, td {\n padding: map-get($spacers, 2);\n border: 1px solid $border-color-l;\n }\n th {\n font-weight: map-get($base, font-weight-bold);\n }\n }\n}\n",".article__footer {\n margin: map-get($spacers, 4) 0;\n font-size: map-get($base, font-size-sm);\n}\n\n.article__license, .article__subscribe {\n a {\n @include link-colors($text-color, $main-color-1);\n }\n}\n\n.article__license {\n color: $text-color-l;\n img {\n height: map-get($base, font-size) * 1.6;\n }\n}\n",".author-links {\n & > ul {\n margin: 0;\n & > li > {\n .mail-button {\n @include clickable($text-color-1, $mail-color);\n }\n .facebook-button {\n @include clickable($text-color-1, $facebook-color);\n }\n .twitter-button {\n @include clickable($text-color-1, $twitter-color);\n }\n .weibo-button {\n @include clickable($text-color-1, $weibo-color);\n }\n .googlepluse-button {\n @include clickable($text-color-1, $google-plus-color);\n }\n .telegram-button {\n @include clickable($text-color-1, $telegram-color);\n }\n .medium-button {\n @include clickable($text-color-1, $medium-color);\n }\n .zhihu-button {\n @include clickable($text-color-1, $zhihu-color);\n }\n .douban-button {\n @include clickable($text-color-1, $douban-color);\n }\n .linkedin-button {\n @include clickable($text-color-1, $linkedin-color);\n }\n .github-button {\n @include clickable($text-color-1, $github-color);\n }\n .npm-button {\n @include clickable($text-color-1, $npm-color);\n }\n }\n }\n}\n",".author-profile {\n max-width: 25rem;\n padding: map-get($spacers, 2) map-get($spacers, 3);\n margin: map-get($spacers, 4) 0;\n font-size: map-get($base, font-size-sm);\n background-color: $text-background-color;\n @include media-breakpoint-down(md) {\n text-align: center;\n }\n}\n.author-profile__avatar {\n width: 5rem;\n height: 5rem;\n margin-top: map-get($spacers, 2);\n border-radius: 50%;\n}\n.author-profile__name {\n font-size: map-get($base, font-size-lg);\n font-weight: map-get($base, font-weight-bold);\n a {\n @include link-colors($text-color, $main-color-1);\n }\n}\n.author-profile__links {\n @include overflow(auto);\n}\n",".site-tags {\n .tag-button {\n @include clickable($text-color-3, $main-color-3, default, default, $text-color-2,$main-color-2, $text-color-2,$main-color-2);\n & > .tag-button__count {\n display: inline-block;\n margin-left: map-get($spacers, 1);\n font-size: map-get($base, font-size-xs);\n line-height: 1;\n vertical-align: top;\n }\n }\n .tag-button-1 {\n @include clickable($text-color-1, rgba($main-color-1, .4), default, default, $text-color-2,$main-color-2, $text-color-2,$main-color-2);\n }\n .tag-button-2 {\n @include clickable($text-color-1, rgba($main-color-1, .55), default, default, $text-color-2,$main-color-2, $text-color-2,$main-color-2);\n }\n .tag-button-3 {\n @include clickable($text-color-1, rgba($main-color-1, .7), default, default, $text-color-2,$main-color-2, $text-color-2,$main-color-2);\n }\n .tag-button-4 {\n @include clickable($text-color-1, rgba($main-color-1, .9), default, default, $text-color-2,$main-color-2, $text-color-2,$main-color-2);\n }\n}\n",".search {\n @include overflow(auto);\n\n}\n.search--google-custom-search-engine {\n .main {\n padding-top: map-get($spacers, 4);\n padding-bottom: map-get($spacers, 4);\n @include media-breakpoint-down(md) {\n position: absolute;\n padding: 0;\n }\n }\n}\n\n.search__header {\n margin-top: map-get($spacers, 4);\n font-size: map-get($base, font-size-h1);\n font-weight: map-get($base, font-weight-bold);\n color: $text-color-d;\n .search--light & {\n color: $text-color-theme-light-d;\n }\n .search--dark & {\n color: $text-color-theme-dark-d;\n }\n @include media-breakpoint-down(md) {\n display: none;\n }\n}\n\n.search-bar {\n @include flexbox();\n margin: map-get($spacers, 3) 0 map-get($spacers, 4) 0;\n}\n\n.search-box {\n position: relative;\n width: 100%;\n max-width: 22rem;\n @include media-breakpoint-down(md) {\n width: 100%;\n max-width: none;\n }\n & > input {\n display: inline-block;\n width: 100%;\n height: $button-height-lg;\n padding: 0 2rem;\n margin: 0;\n line-height: 1 !important;\n color: $text-color;\n background-color: transparent;\n border: 2px solid $border-color;\n border-radius: map-get($button, pill-radius);\n -webkit-appearance: none; /* fix iOS don't display box-shadow properly */\n @include transition(box-shadow map-get($animation, duration) map-get($animation, timing-function));\n @include focus {\n box-shadow: 0 0 0 2px rgba($border-color, .4);\n }\n .search--light & {\n color: $text-color-theme-light;\n border-color: $text-color-theme-light;\n @include focus {\n box-shadow: 0 0 0 2px rgba($text-color-theme-light, .4);\n }\n }\n .search--dark & {\n color: $text-color-theme-dark;\n border-color: $text-color-theme-dark;\n @include focus {\n box-shadow: 0 0 0 2px rgba($text-color-theme-dark, .4);\n }\n }\n }\n & > .search-box__icon-search {\n color: $text-color-l;\n .search--light & {\n color: $text-color-theme-light-l;\n }\n .search--dark & {\n color: $text-color-theme-dark-l;\n }\n }\n & > .search-box__icon-clear {\n & > a {\n @include link-colors($text-color);\n .search--light & {\n @include link-colors($text-color-theme-light);\n }\n .search--dark & {\n @include link-colors($text-color-theme-dark);\n }\n cursor: pointer;\n }\n }\n & > .search-box__icon-search, & > .search-box__icon-clear {\n position: absolute;\n width: $button-height-lg;\n height: $button-height-lg;\n line-height: $button-height-lg;\n text-align: center;\n vertical-align: middle;\n }\n &.not-empty > .search-box__icon-clear {\n display: block;\n }\n & > .search-box__icon-clear {\n top: 0;\n right: 0;\n display: none;\n }\n & > .search-box__icon-search {\n top: 0;\n left: 0;\n }\n}\n\n.search__cancel {\n margin-left: map-get($spacers, 2);\n font-weight: map-get($base, font-weight-bold);\n white-space: nowrap;\n}\n\n.search-result {\n margin: map-get($spacers, 4) 0;\n font-size: map-get($base, font-size-sm);\n line-height: map-get($base, line-height-sm);\n}\n\n.search-result__header {\n margin: map-get($spacers, 3) 0 map-get($spacers, 2) 0;\n font-size: map-get($base, font-size-lg);\n font-weight: map-get($base, font-weight-bold);\n color: $text-color-l;\n text-transform: uppercase;\n .search--light & {\n color: $text-color-theme-light-l;\n }\n .search--dark & {\n color: $text-color-theme-dark-l;\n }\n}\n\n.search-result__item {\n list-style-type: none;\n a {\n padding: map-get($spacers, 1) map-get($spacers, 3);\n @include transition(none);\n @include clickable($text-color, transparent, $text-color-3, $main-color-3);\n .search--light & {\n @include clickable($text-color-theme-light, transparent, $text-color-theme-dark, $main-color-theme-light);\n }\n .search--dark & {\n @include clickable($text-color-theme-dark, transparent, $text-color-theme-light, $main-color-theme-dark);\n }\n }\n &.active {\n a {\n @include plain() {\n color: $text-color-3;\n background-color: $main-color-3;\n .search--light & {\n color: $text-color-theme-dark;\n background-color: $main-color-theme-light;\n }\n .search--dark & {\n color: $text-color-theme-light;\n background-color: $main-color-theme-dark;\n }\n }\n @include active() {\n @include transition(map-get($clickable, transition));\n }\n }\n }\n}\n\n// google search\n.gsc-control-cse {\n *,\n ::before,\n ::after {\n box-sizing: initial;\n }\n}\n","$base: (\n font-family: (-apple-system, BlinkMacSystemFont, \"Segoe UI\", Helvetica, Arial, sans-serif),\n font-family-code: (Menlo, Monaco, Consolas, Andale Mono, lucida console, Courier New, monospace),\n\n font-size-root: 16px,\n font-size-root-sm: 14px,\n\n font-size-xl: 1.5rem,\n font-size-lg: 1.25rem,\n font-size: 1rem,\n font-size-sm: .85rem,\n font-size-xs: .7rem,\n\n font-size-h1-xl: 3.5rem,\n font-size-h2-xl: 2.5rem,\n font-size-h3-xl: 2rem,\n font-size-h4-xl: 1.75rem,\n font-size-h5-xl: 1.5rem,\n font-size-h6-xl: 1.5rem,\n\n font-size-h1-lg: 3rem,\n font-size-h2-lg: 2rem,\n font-size-h3-lg: 1.75rem,\n font-size-h4-lg: 1.5rem,\n font-size-h5-lg: 1.25rem,\n font-size-h6-lg: 1.25rem,\n\n font-size-h1: 2.5rem,\n font-size-h2: 1.9rem,\n font-size-h3: 1.5rem,\n font-size-h4: 1.2rem,\n font-size-h5: 1rem,\n font-size-h6: 1rem,\n\n font-size-h1-sm: 2rem,\n font-size-h2-sm: 1.5rem,\n font-size-h3-sm: 1.35rem,\n font-size-h4-sm: 1.15rem,\n font-size-h5-sm: 1rem,\n font-size-h6-sm: 1rem,\n\n font-size-h1-xs: 1.05rem,\n font-size-h2-xs: 1rem,\n font-size-h3-xs: .95rem,\n font-size-h4-xs: .9rem,\n font-size-h5-xs: .85rem,\n font-size-h6-xs: .85rem,\n\n font-weight: 400,\n font-weight-bold: 700,\n\n line-height-xl: 2,\n line-height-lg: 1.8,\n line-height: 1.6,\n line-height-sm: 1.4,\n line-height-xs: 1.2,\n\n spacer: 1rem,\n\n border-radius-lg: .8rem,\n border-radius: .4rem,\n border-radius-sm: .2rem\n);\n\n$spacers: (\n 0: 0,\n 1: map-get($base, spacer) * .25,\n 2: map-get($base, spacer) * .5,\n 3: map-get($base, spacer),\n 4: map-get($base, spacer) * 1.5,\n 5: map-get($base, spacer) * 3\n);\n\n$z-indexes: (\n actions: 996,\n mask: 997,\n sidebar: 998,\n modal: 999\n);\n\n$layout: (\n header-height: 5rem,\n header-height-sm: 3rem,\n content-max-width: 950px,\n sidebar-width: 250px,\n sidebar-header-height: 3rem,\n aside-width: 220px\n);\n\n// sm md lg\n// | ------ | ------ | ------ |\n// 0 500 1024 -\n\n$responsive: (\n sm: 0,\n md: 500px,\n lg: 1024px\n);\n\n$animation: (\n duration: .4s,\n duration-sm: .2s,\n timing-function: ease-in-out\n);\n\n$clickable: (\n transition: all .2s ease-in-out\n);\n\n$button-height-xl: 2.8rem;\n$button-height-lg: 2.3rem;\n$button-height: 1.9rem;\n$button-height-sm: 1.5rem;\n$button-height-xs: 1.2rem;\n\n$button: (\n padding-y-xl: ($button-height-xl - map-get($base, font-size-xl)) / 2,\n padding-x-xl: $button-height-xl / 3,\n padding-y-lg: ($button-height-lg - map-get($base, font-size-lg)) / 2,\n padding-x-lg: $button-height-lg / 3,\n padding-y: ($button-height - map-get($base, font-size)) / 2,\n padding-x: $button-height / 3,\n padding-y-sm: ($button-height-sm - map-get($base, font-size-sm)) / 2,\n padding-x-sm: $button-height-sm / 3,\n padding-y-xs: ($button-height-xs - map-get($base, font-size-xs)) / 2,\n padding-x-xs: $button-height-xs / 3,\n\n pill-radius: 6rem,\n\n circle-diameter-xl: $button-height-xl,\n circle-diameter-lg: $button-height-lg,\n circle-diameter: $button-height,\n circle-diameter-sm: $button-height-sm,\n circle-diameter-xs: $button-height-xs,\n\n font-weight: map-get($base, font-weight-bold)\n);\n\n$image: (\n width-xl: 20em,\n width-lg: 16rem,\n width: 12rem,\n width-sm: 8rem,\n width-xs: 4rem\n);\n\n$menu: (\n horizontal-spacer: 1,\n horizontal-item-vertical-spacer: 1\n);\n",".popup-image {\n cursor: pointer;\n @include hover() {\n @include box-shadow(2);\n }\n @include transition(map-get($clickable, transition));\n}\n",".extensions {\n margin: map-get($spacers, 3) 0;\n @extend .d-print-none;\n}\n\n.extensions--video, .extensions--slide, .extensions--demo {\n position: relative;\n width: 100%;\n padding: 0;\n & > iframe {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n }\n}\n\n.extensions--video {\n padding-top: percentage(315 / 560);\n}\n\n.extensions--slide {\n padding-top: percentage(487 / 599);\n}\n\n.extensions--demo {\n min-height: 340px;\n padding-top: percentage(315 / 560);\n}\n\n.extensions--audio {\n display: block;\n max-width: 100% !important;\n}\n",".article__content {\n p.success {\n padding: map-get($spacers, 2) map-get($spacers, 3);\n background-color: rgba($green, .1);\n border: 1px solid $green;\n border-radius: map-get($base, border-radius);\n }\n\n p.info {\n padding: map-get($spacers, 2) map-get($spacers, 3);\n background-color: rgba($blue, .1);\n border: 1px solid $blue;\n border-radius: map-get($base, border-radius);\n }\n\n p.warning {\n padding: map-get($spacers, 2) map-get($spacers, 3);\n background-color: rgba($yellow, .1);\n border: 1px solid $yellow;\n border-radius: map-get($base, border-radius);\n }\n\n p.error {\n padding: map-get($spacers, 2) map-get($spacers, 3);\n background-color: rgba($red, .1);\n border: 1px solid $red;\n border-radius: map-get($base, border-radius);\n }\n}\n",".article__content {\n code.success {\n color: $text-color-function;\n background-color: $green;\n }\n\n code.info {\n color: $text-color-function;\n background-color: $blue;\n }\n\n code.warning {\n color: $text-color-function;\n background-color: $yellow;\n }\n\n code.error {\n color: $text-color-function;\n background-color: $red;\n }\n}\n",".article__content {\n img.shadow, .shadow > img {\n @include box-shadow();\n }\n\n img.border, .border > img {\n border: 1px solid $border-color-l;\n }\n\n img.rounded, .rounded > img {\n border-radius: map-get($base, border-radius);\n }\n\n img.circle, .circle > img {\n border-radius: 50%;\n }\n}\n",".icon {\n display: block;\n > svg {\n display: block;\n }\n}\n","body,\nhtml,\n.root,\n.layout--page {\n height: 100%;\n}\n\n.layout--page {\n &.layout--page--sidebar {\n .page__viewport,\n .page__grid {\n height: 100%;\n }\n @include media-breakpoint-down(lg) {\n .page__main {\n @include overflow(unset);\n }\n }\n }\n}\n\n.page__main {\n height: 100%;\n color: $text-color;\n .col-aside {\n display: none;\n & > aside {\n position: absolute;\n width: map-get($layout, aside-width);\n @include overflow(hidden);\n }\n }\n}\n\n.page__main-inner {\n position: relative;\n @include flexbox();\n @include flex-direction(column);\n min-height: 100%;\n background-color: $background-color;\n}\n\n.page__content {\n @include flex(1);\n width: 100%;\n margin: 0 auto;\n @media print {\n padding-bottom: 0;\n }\n}\n.hide-footer {\n .page__content {\n padding-bottom: 0;\n }\n}\n\n.page__comments {\n margin: map-get($spacers, 4) 0;\n}\n\n.page__aside {\n .toc-aside {\n padding: map-get($spacers, 5) 0 map-get($spacers, 3) map-get($spacers, 5);\n }\n}\n\n.page__actions {\n position: fixed;\n bottom: map-get($spacers, 5);\n left: map-get($spacers, 3);\n z-index: map-get($z-indexes, actions);\n display: none;\n}\n\n.page__sidebar {\n z-index: map-get($z-indexes, sidebar);\n display: block;\n width: 80%;\n max-width: map-get($layout, sidebar-width);\n height: 100%;\n background-color: $background-color;\n @include split-line(right);\n @include transition(transform map-get($animation, duration));\n @include overflow(auto);\n .sidebar-toc {\n padding: map-get($spacers, 3) map-get($spacers, 3) map-get($spacers, 4) map-get($spacers, 4);\n }\n}\n.sidebar-button {\n @include clickable($text-color-d, rgba($main-color-3, .75));\n}\n\n.page__mask {\n @include modal(map-get($z-indexes, mask));\n cursor: pointer;\n}\n.layout--page--sidebar {\n .page__main {\n @include overflow(auto);\n @media print {\n @include overflow(unset);\n }\n }\n}\n\n.has-aside {\n .col-aside {\n position: relative;\n display: block;\n width: map-get($layout, aside-width);\n & > aside {\n &.fixed {\n position: fixed;\n -webkit-font-smoothing: subpixel-antialiased;\n }\n }\n @include media-breakpoint-down(lg) {\n display: none;\n }\n }\n}\n\n@include media-breakpoint-down(lg) {\n .page__sidebar {\n position: fixed;\n @include transform(translate(- map-get($layout, sidebar-width), 0));\n }\n\n .page__actions {\n display: block;\n }\n\n .show-sidebar {\n .page__actions {\n visibility: hidden;\n }\n .page__sidebar {\n @include transform(translate(0));\n }\n .page__mask {\n @include modal--show();\n }\n }\n}\n\n.hero--light {\n .article__info {\n color: $text-color-theme-light;\n }\n}\n.hero--dark {\n .article__info {\n color: $text-color-theme-dark;\n }\n}\n\n.page__main--immersive {\n .page__header {\n position: absolute;\n width: 100%;\n }\n .hero__content {\n padding-top: map-get($layout, header-height);\n }\n}\n",".article__sharing {\n margin: map-get($spacers, 4) 0;\n}\n.article__section-navigator {\n padding-top: map-get($spacers, 3);\n margin: map-get($spacers, 4) 0 map-get($spacers, 3) 0;\n word-wrap: break-word;\n @include split-line(top, 4px);\n & > .previous, & > .next {\n width: 50%;\n & > span {\n font-weight: map-get($base, font-weight-bold);\n color: $text-color-l;\n }\n & > a {\n display: block;\n @include link-colors($text-color, $main-color-1);\n }\n }\n & > .previous {\n float: left;\n padding-right: map-get($spacers, 2);\n }\n & > .next {\n float: right;\n padding-left: map-get($spacers, 2);\n text-align: right;\n }\n}\n",".layout--articles {\n margin: map-get($spacers, 4) 0;\n margin-top: map-get($spacers, 5);\n @include media-breakpoint-down(md) {\n margin-top: map-get($spacers, 4);\n }\n .card__header {\n font-size: map-get($base, font-size);\n }\n .card__image {\n & > .overlay {\n &, .card__header {\n font-size: map-get($base, font-size-sm);\n }\n }\n }\n}\n",".layout--archive {\n & > .layout--archive__result {\n margin: map-get($spacers, 4) 0;\n }\n}\n",".layout--home {\n .pagination {\n margin: map-get($spacers, 4) 0;\n }\n .pagination__menu {\n max-width: 100%;\n @include overflow(auto);\n }\n .pagination__omit {\n color: $text-color-l;\n }\n .items {\n margin-top: map-get($spacers, 4) * 1.5;\n }\n}\n",".layout--landing {\n .heros {\n max-width: map-get($layout, content-max-width) * 2;\n margin-right: auto;\n margin-left: auto;\n }\n .hero {\n img {\n display: block;\n width: 100%;\n margin: 0 auto;\n }\n }\n .hero__content {\n margin-bottom: 0;\n }\n .hero__cover {\n max-width: map-get($layout, content-max-width);\n }\n .hero__cover--full-width {\n max-width: none;\n }\n}\n",".layout--404 {\n .sign {\n display: table;\n margin: map-get($spacers, 4) auto;\n margin-top: map-get($spacers, 5);\n h1 {\n font-size: map-get($base, font-size-xl) * 4;\n line-height: 1;\n }\n p {\n font-size: map-get($base, font-size-xl) * 1.2;\n }\n }\n}\n","/* start custom scss snippet */\n\n/* end custom scss snippet */\n"],"file":"main.css"} \ No newline at end of file diff --git a/assets/favicon-16x16.png b/assets/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..7477a30c8120ec7d06917d5ce7a4f569c132e06d GIT binary patch literal 655 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+ueoXe|!I#{XiaP zfk$L9kg@_{#t$izynur2C7!;n?2p(Pc^Jhi1+QrUg?@RuIEHAPZ=GoC&Fm=9Hb1g; zv)6@;m9A3Ug1Ss%NfVteYQ2+2Xiqh|#KSRb*u+XN)sGT(YQ>-EiS zX3tZSlh5$xOz6tt-@;h#^#6F0`ritDy{Fo5_AR;Jpcm|NXRBDq(d8a_r7z2wHrLgq zZkjoLVMiXX(Sa?^{Q;*Kck(kmwtNuAROWAG>0rvRr$;8uA$OYA@u+g+sD}-&d1h4f zGo+TEnWvVT8nG=ie(fz;SI3(%cRKdn>I$0OAH4p_{`db__#=X^d}Mpl0}KV#64!{5 zl*E!$tK_0oAjM#0U}&LhV6JOm7-DE-WngG!WTb5XWEgn)HJwD!kei>9nO2EggSD_# zDNutX$cEtjw370~qEv?R@^Zb*yzJuS#DY}4{G#;P?`))iio&ZxB1(c1%M}WW^3yVN zQWZ)n3sMy_3rdn17%JvG{=~yk7^b0d%K!8k&!<5Q%*xz)$=t%q!rqfbn1vNw8cYtS wFe`5kQ8<0$%84Uqj>sHgKi%N5z)O$emAGKZCnwXXKr0wLUHx3vIVCg!08AR_)&Kwi literal 0 HcmV?d00001 diff --git a/assets/favicon-32x32.png b/assets/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..7a610234b78486cfe163a8c5a274f85c36589a09 GIT binary patch literal 931 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-G$+Qd;gjJKptm- zM`SSrgHQ3@o&>7 zv)}ah=zem7)qYv`S;j|si7Pf|FZg1&Tf$z|c+KiluJcZX+~55A>9mHBmuI^iZUA=1@yP}n&;d4t4x5M<)e2q(&XdlV4-|o9;O;4adw}Mc7&gACm zGXH1Mhq*##n#MQ1`MiZ;mNEO3TE@y^20{A+{hZL>u<-JM?^Kc%oZTmnkJE z{N7sggVN--w)YF}^VOtoW_)12ea=a*%Xzj22`TNocRJi{7zEcbe27#lS+U2RS$ns| zs^mT2d=v7Y#$_wk`Muk?VDe}Etq-?!evO)L8LO^)J6c}vDr3;WOWJu>m(PaHeRF;H zn|0I5=X96qF0txLmdVO%KjXBpf2G&QWBa0N7igR~@$sPF(!Zke{nL|w3P)#gbqU?> zm~lf?wXkT(_1M$(_H+L;iElZ&>_JPk5-_o-mbgZgq$HN4S|t~y0x1R~149d419M#i z!w^FwD+5C-BV%m?Aj9B|WoIplhTQy=%(P0}8s?@L9t3KT1lbUrpH@mmtT}V`<;yxP*HeQNJL3cV!1*=QGQxxPO3slWkIS!W2I`Q&6e6=(&6r>mdKI;Vst0NvVlI{*Lx literal 0 HcmV?d00001 diff --git a/assets/favicon.ico b/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..6f48a2dbd152e57b33b5e228f1f1f1f54c60b54f GIT binary patch literal 15086 zcmeI3dx#ZP9LHx}YqNx1vdjv*o2f-vEeJ&|b#E3Mo2~m|eHN}68ree&Au`=X%S@6A z38kX6EF}LZiGoC9EecB{dl;Bv*9WXU&=td7b$6%F@9sU9+1WF9&dhxg?1kU{=KRj@ z_xpa&W9H19nYFAUtJtcju+Zh!=5dyFn`K$$<;ilHWgWs+hL-xrhFaE29B#me5UUvx zd*U~?PYwPZ?15bLKvh-MCAr8OBxiGT^F<8}4fR1{oW6~XjrYx)H}3+cEnmkqH8q#P z{oDHb`rf*_x~XAsg*)*}nwpwQ!^DS;q2I>A{Q%f@QqIr@xl{%I33p;Q*VfjK4jddJ z0N#~w{>Q;xwV@p@*MvxENgVn1k$YO0c#{|zYw|x2PM3zeYQs-(8E%r#?ZX%}OJ4Nn z;6&W@nUu+U2l7iA-ip8I#bL3C-61fkh6|lru@aP!4%K!N9@ZUn7?6P1Ggt>PYrT2 z{WOaHorpAIf0p|7NV`Y8F@N%X=;mcwhSTNXJIBBnH&fn@_;sdyb$;~aCyreC=6{a$ z;T9b)QybiC3&3B_@r`gFW6IOZPtHMd{h7};Fy?YE@sW(ZT(e{zd+{@|lk4Mr^4DPP zk$yDcs`DY=YE90(o(qh_y*h3tHpcPsLh}cIjtN(t59iSXh2~HHx9GT;*x)YrEP0(T zZan6|J0@IpKICe0=PN*Ym5!T<4X%p|z_;IcGk<27aMk(9HMr3H<$hAf&BO-xZ3X7v z$r={wK3blw;a<>tIL3dsNk8j+h<&=ieJ|%h=zCCNe=IbAuAP;-zV)-g`_@8p2Y;U* zPp^7nzbG_+`hKAozjSuyePa%}n?A$Kb?4G_Jau)%42S!%9P-~z?xP0zH|hBK*~q)b zAa}p@F`$1B^I|43(hjdY$EHk~a+M!fcRll_ik#hP@_OL~wI64kuLkEOq}Pc@+YZ4) zUc&_Xai4q>c}_WV<@k6im;!IBm$=^!lxvFa2!b&eib=W0Sou z`1_p$XYl#Vu$}$5YGU?h`<)f%g6w&It-(@UWZ(9^H1*2QPybGi@nQPeFDChZHquBM*?-xv7t*46Xeb|-kU9xqetA^pEFAb+_wY^Oh_!uvuU zPCjk2=D_7uIGy)shrHL?i+q4A#`n5>@-xMvFXcXOJQ78wOaIDqb|!n3rr3jigFTQ* z57>#HWu?=n?Y{cM=pG!~(7otZc2e!A-D`=C*j-i>y~2(sv{XhYx4}+nsgF~pDxt+L zWxiBM>5gRCsd-~-OIc~BzdK%)+wfPoqFX6P9h#z)dr?AFbj-G_ZgeHOORCWV8*yl5 z7eA?&xE8d%91(reDoJRYYAGjv-deuXDo0{S>N~A63WGmw4{&Xj-YL>nW?*D3ohI*} z4N?^0SCi>?1Wp{*jOP&f=I1bL$tY)??pW4te3uh5Qde(klQxj!EyQ_W^8@*Z`Q^Yz z?hobOE!6uU`uiSY_q(takJtV1rJg>Rn~eX8;}|C2c_Cv=%tjAfg@2m%nS4K0M4oRP zxen5x!|3 z5s%pR*%Y2F`xNrvU5=#oDMEjVNs=@_=5U3{^R+z7F~`=rQ_0%Je0!I3dyKnIm8qYD z$U7K0HwQdxCGTnF-TgZF%m8aNdLrdT$d`!ooIXK$gvzZnHrcm!`gt868}F z#4x^{epsq-m8SjU4C95@Ru!i-Iqwb-zd^+*O@Flnh#&C&5PaizvF__Q*z&yX)!$-2 zipcu~IsO^Wsl5YYD;#xRIpo`g`S>kB`i(vIwb;Dkoc5 + + + \ No newline at end of file diff --git a/assets/mstile-144x144.png b/assets/mstile-144x144.png new file mode 100644 index 0000000000000000000000000000000000000000..1eddae64ebec7e62679413bc5d29b0ff7ddd495f GIT binary patch literal 2908 zcmZ`*XFS_o8~zJU@8s+ik`Msas5ATO_uKPaczV36*_2pbi=BE0rkn<1#09XwTbS!C@@$X<_ zpv@*`(h4+S@X<2S0)WQ!b0=6%#03dXRI$G8t)7ypNfi}H@ z{hi^$p%(Crj0{Ys4klS00iUw0oXEtgG1>EMqF%%DtZ%?u5_Hx{?pH};1D*Neu zFtP&wI#PBJg@W<%<>=hjVs(Xo&*N5PhmVHuZwqH;_lNEjH6MIyExx+*wXLml=j&Ya z=sf7w$G;5;5+?bIv;ddRBqs&&6yuG_Nb&Wgr|3vJj3>gJFwZDsvT?RiDtuOGdM~7& zP!m*(0V9ATkHzob)TY0UW9c_fA>8CHBzZAg!(mG^_KE0$2G;XEp#@s3n%!CviE4^T zKeBlf6{w)+@a4S#oMI`A(LCu?C06gD_K^%UpvNJ5Kqqp)vMYQlO?o2vH|8~6i4HtI zGX_Z-lU}sbkm{N@e~u`|DV9J27X1WxoDIdG`{wb_&}=;!arYX?CTVX zb1G>XQ;C9$P8p)1nG(Z|PVWywGM{q!C_P23?W3+8ikz0j?Cj|ai~w!OA7o~nz!z$) z;qRH&_=j|nE57@>B}I0)#H${S{<5jdQp-U?#Nh~DJ2Zrfo?mW7Sc(f%Liw3N45B3SIbqVtuI-pR(nGb`%jbKF*CUrriJ+&;7HJj+2zY&mE ziWz<_QcbtsSr$zA zRZBXQhUk)Y0(K=HC5Fv?4qISxnAE%J0#{zdDXzDCQDU^If0+kCB>oCH??;lougmz& zLa;)YAhCET_$UtZ1&Vm7Rk4ATorxHaT5B{VPRdP*npWdKu9hQ5JismMEi)o78ov(; zBXhfCu&f~h!NaO-o0-V{x3)E1X5mjNr7{wBb7RMm7;awk1ngEX^E|9UHN^)c^t5da z*$)8M1cq{INVk*gQ}ho7>JN99rmj2BDEot5@EzCp z7q&((QQd5tXXKp}9*@X8Mw}mXCfT4W@C4QT`lcSOsmq#%e+sch2$GVPW7#>UZm`^{ z=B1##{p(`N9`l9vF}atfhoINM5NdKt>!`p9h-IlsrxsC1B%apfLY1_mz?r)C znDE&0BZytIWEp>Tpc7Hl&OE_scF93#0e154y3$?M4n!688AFx#8njy_-H4vuwTa;R zV{Kl#!MR?#r5H|G~I$PQ`UnJ}yH< zvmDL;EL};R2JgW$62B8wYD{1`5%gSJ(IL`hi_m|dR8rej?gh;@xXUK>#%`4VW;QCJ zr-nX++n}J^f!uMbT#D>v*#y3Ig6hk$#CwoLSNy$e+t=Z{H%?T=r%P|~dHu3~F5Jj3 zyxC$(cddpvY`}!TFDcD^mU4S;BTjJKm`_w7)mptn*l+n|b5T4_jA%{xq77HsD;PMP zLj;+__`ZNGB0ijyZu#eHNTDgY*iUo8S)ioj>`Kpy)qcH|0}nrwCPBC!%R8NB{XNij zzs1~dLormZFUZ!VXke}%n}~UR_I-=;;57bW1;4r%SD(pOyQw1 z?QsJMT?sB}m`3iKe@Cg8_k>^>IOn8L^-MqaP2g~GhU83lmuVM6ff+>E_)cr|N;a+AxW_RayO}{sAZPaCh%xX$+aa0a0|uc}8%ec<=d7D) zg?-gXEqWA9fRDa?j(zObOo}W$%VOhLaa#z1`2r6phyPM8f_X0FLQ35FV9`&JTk$B3 zfgwo*1;Z;3?T{gEt3z8dzieuiJqZS>t&m)2}z zM>G6Q_tBW_x(Udimb9s>S>Z+S{$#CK4sHA65F*PF^3^xD_3Ln$N2y1T zJMHH(ErY!XgsqK5n+8WGiY5yzyFnp1!T3_9Bf5jCvildqz8Q^y{wS;^mI^U83ZLmI zZH%Xy?)z(qQDP#p-PI-BL0FK3!A6s#;(`4eRe>{d8jMbj%6#9Aw<@rEyVjLOOxOd( zs(>Cp2#qe!l{%sN^hg??Ug-_ZzN1>K+eEBrV1E$=K4_ZM-?5pY=;y4O)F>F&<+L3# z=~uRk$I+`KY)0H=`<*Jh=h@T^e+lznRX=Wu96(A^b%mOD1J{Zq)Tn2Rnr#o7R?g#G zRri2k*gz|9NQLk=$<)5ruDGm4IxG zt{Ho0u(gb};ke_hwV_A6`mYLF~cB7mHW<$eJXli1BJWLiKGY&->lwjq64PK7mJ#D2N+7W=EuBlF)wrli%0Nv;` AkN^Mx literal 0 HcmV?d00001 diff --git a/assets/mstile-150x150.png b/assets/mstile-150x150.png new file mode 100644 index 0000000000000000000000000000000000000000..1e3579a37590846f82c28dfec8742cc25c761703 GIT binary patch literal 2896 zcmbuBdobI$qe{p0=PU1zVg*R#Lh{e1TG$6o7MPxdhnyu6IM z3;+P~t}Zxl006=M+)zoe^SasGOdKS`9o!uNpgvc2^Xv|>)(&>@b_ak20{}=#2Y?@9 zD`gP?Vo?CF90&l|A^=d0DrxmPAij_Ya>L^QQD$y$hgd;kT-}`^a}bamNIk0J7cc-w zue;(LPQ<Z!MSZaoZAoW!@X-q9S} z!4&YjRP4u2(EhM!*M{HVZPNQ@s1HScaQAn)-)OsH(F?JJWSJ$^4~;jtCX2!V&UcNk z_#61<)ZA5r55CFFqp}Pe2tqC6{82Ve?t13&N*2Fzt-n;Xq$)f>{;_31g5ym`W=&TL z)_J?`qqB~GSGm&II4=6MPV6NEfjTx{`HAJvJ=!^JOwW(wZjN8#ER#FSUtI1ss|~}@ zjkI>t$(t(yQ(bt%*{!AD6-;>Zn^*2@kfiChV-|F-_A50T66}wT#{5)#?(l|Gu(b7A z@X8!J@ne)!e?(HD{s=6~~of?sro{yT~Bzw+PzLye;=}8L6*H}S&K5T-A$gVVM6<4!~_9e_` zhYG@ni7@iLqRi${t*9AB*Oiumz-wbMtUsms}M;z*oTEC z;4E0FWSWGV5J|Z@c0o6%8u8F+hQp9BVH8Tuy!OSOPJ8mVm8$l&Y!Zu;xP>B4e*P4^ zqh$1YxDYIv*x)XUt@#TA#d(>q6qFkKhca2`ny7g&SOHjorKuS!KI<$DRl=ndsPP+rS$N)YINC=hHC-3UE39Fg#8bD75|#GO`q17ND&q&KLf2CL_c@*k zB8#4Tx|tr7Sl%b*aF+Uujol07Mz89hgA|79hki1-Y*QCl{_2nJyv!bz_Gs0>R@bQl z3=vkhn{zBu(2YSJiMVn=Qh}W3@RtoV5iSX8tM*j%b?K$G8Ai9l&%imeuN|hM(6Qix zVJ^^iEl%3ZbjdmwQG@u-i^Blb3=clkwUT+158cKDz*bPFuwN!`<#;w*jm+i3xQ;Vw*25KH5t7kSAeS(@bc4?REb-CMH zBcS{!J8<&AvF0A3L&~T;KO#mjY9{Z4@z{~j$M~EvbDj^)PJaRqhqqeW61Xp`ts6D; zZu_Kr|80B4_~Heod(vtUdOK^$CDD~|CCP1Jz^oBlJJUWllPJz=wk%3oVBV^Fei`b6 zk&)OY-gS%JA+k8z+~Y-&;6vM;Pf~jyL%*=vTQ6An`s*2rSyUz!V}EF-upQ=u>Gp%T zFk$=s@h*BeZeXnwH*3yxpWp&Gy%LUOpvC;BPHSs9dbnV!d(Qo`p_8tfLKsD~6S z*cS={<_OtVBRZQ=yoX*vmL-)5vS&KKwORAklF}c+O%!H!(#-dl%eEkKW^-9{lFKyp z$OLLHR@xmC6%6pAdD;qiA#BPSQFgA=0lqd-OxjsS?sd$LP3OcRyXDo=st8o8GcyAh z(pB7=yS^xCWaC;=pAmAs8;I!iwP2acP1L~(;zZipk|0e4M*RJDZzPjnYd#7zG_G;d zL#S}+YQ6Q;8O~%;q^8d9StQ$zYO<;BaMNv|Sl$6c$R4m=!x+#X@NgNkK4X~XzEOS_ zZ2to#cl1r`7~Kr`Lb)Or>1=q!t0;Bv)ajcR{Mprk4RQZcFeqYcvPd6U9v_(?xC7$- zV76JO-RrbW_`2JYzLT^SX^M<8sBP2C?{dWGt+5QJK`wYs6K z<$zOimh}kQ2hbFq%y(OoAO^p4f9h&H_irs)HDh*jZI=i3)7J1Y23H7TKA;mv3B~=TzY^(H6ZloR z!l+6wiiLgg8_@C@?D}uy@cR4?{gxc7yPoGl)A!6L+CLd${!xQBHAdxnT6V9u4Pm9} zL&POMd^xwt?G@C%^ERGHx4g167@CktG7~Yc%MG6QgLlSHjN`4oMm>)0 zuBIypt@@vO47t@tRKIYvTE+}uPoJ70m<1i;6-C2G8=n+LYXm$nj($Vz*4c#P{VNs` z0s6^P^Aq^JsyiYeYJMg`v4a)s(Wt_Pc}J^$;cltz7N`#W0F`_;Urx3qt48mYSv%dE zf50~-jqrilr~*of-!$-7;~8NEDIJP|T9dvA#a`}cv+m;aL*`7P=7oPW3;lnv?Y1nX zV0iKlTl&ulQ9s6+5EF7XCKMY?4iyK$0%eXiMcJ65%ukqGU{U5+3u|MP_<(8$J$nCN zz@^BLh_Lwo4Ga*q!o+}2!U;l*_t`7jQRK+5h>M}xG4WBM+7VGP!2l3fGQTPZC7nVd zdptS4u3T>bjF2@8mQ_Y5yWLg+D{JFY_On93h{hAzoOf?tcJ_9{J0YADm*0+<0n4gN UF{_SNcf=(CS7#4gtz#hNKMVL)C;$Ke literal 0 HcmV?d00001 diff --git a/assets/mstile-310x150.png b/assets/mstile-310x150.png new file mode 100644 index 0000000000000000000000000000000000000000..e364eadd910b3309ccc345a33324a56f84733ec5 GIT binary patch literal 3150 zcmcImYao+-8^4DPIXof?LupzmM4}bYz~`K zr6;Gt919B}Gf9L(^4|5nU)~SzxA(cf{O{}fU%%gVy#M$Ax{DJ=MoL8r000?#JG2`B zi0A?UL{>r!)QCYnHhwMVEFCQY;3;+Uk5ijKS@VpYnT|m#C|^GsuNC z{UAHJ(8=+r)8Xm6+4*k1+cs_AacR#^Zw_n}Yce%$6L`V&+#GB9NMrr-YTWzh?>{Yl z+BiIOidi|Zx&a5H`6r*Q<}?BDP_dk+Ps^87VFnqM_``5LVWIunq21!4{j!zXH=VA3 z%ExN9l8_j0T%z{*e4H%hiNh?87gRQ&vhEl^pc2{@h7&iZV>O+7%eYwU%@o3;DqLA+ za7iY@KC;aQOLw+QrT+0$rAPR#GTxjBYTH1}!+ZRQt`XTK=6&0w zffdP-33sgWx~!|u_X)QpY=LV|d2r6-+eg_p4ofwe+H1=etHJz&pC!iKMKZpO=EY+b z@YQt@f#~O{l7=QGcQ(OU^`3e2o3sgAi)>+J`=!AjY2ouVf+(5%$M?T`#1G8Koe-t@ zj|R2gsVl!Sxsd1WKc;HFZ+3@%PY+pqGL5YMfXubeh#eXYG`7K@MhdV79WptOI@3q{n+Ql> zQHECjoU5+S0Nry?6~=%X4gZ;kNq2Y!r>)*>C~C`nTw)W-lkfV5$a?KCNUCipyD3oP zN4wV+{DygI2KTA)i^~OSn8mjE;s;i_F1nsOf_Wk4O0*9W7lkVViEAj1$MwLJhKD0I z#U_u7e3f7jc|=y}?K*nrOrJn8Nzi58+Oe_DGb)eLQm~(d1ju`B4PdMp86f|LDB-;- zJk!kVif6EnJVYO^*uQ79?+fC4lp>U;O)+N)C4%^G)hQNPHrv!2hiTJ2X zHzF=KYVZ*oQ( zHqCS5w7A|Ob6+YO+L&9vye>A=ro09eUm9m!_%Qo|y{6l#6Fruu!8!0t`^+)Ugzd|% zYiT6LJntNt>9nP8Kbp#9JW4MXC?_McPL=ie(_DHzJsvHNO=wt>U6rtC@1nG=T=!W^ z)sb?FQq!12 zA=Q{$4R8Wb{^^pFZf`QrTR!uOiC3T7;wagYVnD~19`7!y%_zE4Sn#(9&hrYP1lNi) z*TfVfUYu1|g+Yp}I3Beawi(h&UOU*9D3&fY={A{WY-C}b=Z9O`@sU!Zlu7 zrMbTiZt=4;6O89x;YWdlT?(OIjmeORJsy#lj ztw=k>M=@P`GHo}WA*whQ&3fSV%2k)VkjZ6eDTdUEqcJFCxu-k3g7-GltI`(Jk5o^wb{pAYQ=<#!jVXT*@K=%g6Wk8O8Cr%&?bk>wk^KrX) zJ`zkCG;Mt-O^YNuoB&gXwZWaW(NM$-gW2TY{KFBUU`l?SyKc%}0^R-C_V_x;Q-#$7 z)A&_^8$EBq3gCM9t*-=s{H~C@AwlP4*2kIKpsZ)A?75>X(r`Y&KIQX(ahf^rz%BL3 zG=;E!wm=&EGIdF%Ua6&?S%a0X4wWe9-S4tV;{1HDO%qc0of{j4Y=1o=Jk-6mYMN9} z3q1E~`(gs2H6>d`4L0`2b#WmYR%}SBR=2My-_)Wmw|v^W;-bY^4+A4vlyTKP)-JrX zSyAljKLsUu5x!GMUEyBS}!}0 zV!_7rp3l6;u+H}YPm-yz)xe;*j@`k>evNg2jdx=T$;+~nVD)N#sRSw&kNrtPb02hV2;B`@o3=zsNwZdlo}f z7-xw0wP%Qq-2wyM)iLR^pj34i-M-HSlo5H2p|T8$YbQRtA3sO`TD&W>ul!==`8!_r zX{ttU#rVXIp7e@+)m-->22>gaF&m6GdtrmuiA~ZYuwqCL$DhYFPJQ26o$>uN*G7HY zuajx?1fSF*Ti4|LU4=UBSElww*!1UBjivrAneDSYE%AKdNE1_o{WWafIhL;KP?Jh= z{$?ImyQ{CF-LRlp8buu*h04AqRj*k(M{QS9z7U@`sSzD1B#=8Yq9Wl5msiHa$Whh& znP&5zv)DoYx;bAi#yc4o{H>Vr3vI@0U!G(8{bVserzjMKec7is-{7Cwep)U?-3Ty` zsE@CS&*BR?N2=ti&0Yjd_iexH*I-8J1r}QdDAlu0ddoJS$MM;2c!{uvXcE>C~y_+ z5*=7^ii)N?-*nnZuA5RO%FKN;*(I_!m-$V*^TO5P6Q3^qbfbEI`PqMr1$ZC>K3J^y z4m^l2N`nih+UrX1;e4hI3k*dBmqy0(RvF+Qrj=BcrAGHXv{oS?0+dn7+*v8w_=rM% z7cGfXH5u8_rbb5jldo8e&GYtfTaAY;jtB;`4t16nikbjPW7^Kl#j}UaTD}gihE;z0 z>Zkq(O%hd&ox0$2H?TD|ca~o@>ohX6gGUC4?dxW2HHAIta6GN?lTy@*r7`9l1Wbn$ zKup}(-M_lIf!QrL1_a*1A080-CogpWH*WNr@i3x?Pmlad5#)puZH^NIPZ7@|&k)Xn z3t*ssz+}I^@qYaS9tRAN`Uj8(#ya}oi~hEYWnupX2n`Pm4kG_=K(Nl%ZV=#2@imM1P)8G(0r0I;`lLYG^e{Oez4 Cv1M5R literal 0 HcmV?d00001 diff --git a/assets/mstile-310x310.png b/assets/mstile-310x310.png new file mode 100644 index 0000000000000000000000000000000000000000..1e4181b5420bc1f8d6e5b41d2cc98d634801572d GIT binary patch literal 6221 zcmeHrc{r5s+xJ98O1>ic+Dl}~Afkq$Fa~2CqQ;=IWC_uXWe_bAvJYd)IvCreY?EzN zl290oDN8DbF;cd%jPTq&zxSW_kN1!F@8`LX<38@|ywCHzuFrLT&hxzPnV2o&=O z1mXg(Vpt#$Rs#fLUk8C8*&vYkoo6j}`hbGxCIW5(+KWs1*a1j<7;`Ha-w(chNA^kF zA?5OdKw>NACYK$;#ug{TpUGmQx|cV8dSzJYh@U)kQBLz8vluyw(IF+2wfa$*0NJ3l ztKK4!93|ICw;^cv$vA_AK`7m zfa~sC>BUE}-VHTsY)48{g>ycNIM?Xt*XZgpmW_qqGL5nW3x0F7qVuSY zENDBo`aT<@Y*RO?oShr4Ei@o)=7ZE2TBtH>tY;KW{&K!8IDYkn+K3*ubhg6%Ng|7J zm_1UDs)Db*$nq(3E%JKdjz_%JckE_I;W89wnm)*ohXvH=#yE!*-&ogg7X)1endxU` zOAcd(Qa7AJO5qC)mO^Y9XlXaShkB$5)_l50ehutW3Pprf?jtjXakhqx0ZzrJ#}3UM ze#I7iXobJst&mnLD!qDl(c`^fz` zBICdhTX4^ET8ni((Vhi^k`-F6B{nA1T6{8Tb|@Ivwsxm0Ry(U#s7ikRWn(jFxIm`m zM3dotn7DvZ7hLkHg_Uu$LsqwM)2z>Z*Xl14Bs+=0L=>qTLlB#PSu=O#-U5_+CMltx znk3G762ORN$s~7yiuHRBM-~KfR6@6PIT5iP--(m!0v#8>8e*=HH-7*-QS~}eM*(`dk+Fjk?qaRVy{S0hbf4V1dX_nP=;(>KEEyAQj&J zOh()$U1cnWuo>1bSN2#}`^g#hbcUNsl@ zay>GYTArw_MmezC)Ss$d_1+0>Gu*{}*4LgP8C!z3n4{e*+_fTA`ET}d8`B`&84a$y zoo)`>X5T3^m zgO#7^t&zA7$_d;OvTu@1Xux}xC}S)A^?M>(;Kw1Z>JBV9B_^=M{T&*6-n3ctZ7cg` zR84ToW<}m4XN<*v0@7OsDkw*A_!_BwB#ANXhb8d`O1DAHt1>MPB;%#yu*_#&D-V{SgR?j+q{~~yv<&eP5$S!#u;v-?!e|%w zoFoifa1%NT1Tm%tAwFNtPU%mJVWOExH$38GT@n|uqs1p5R|yv~g})xO10U#taLjk) zAh1u++Bl;8b(Rw0t%oX>Dh4A}G{$ipF#C^4Gm%BNEBoEM@16Md;gwg#dnJOAr{Qoo zN+!Rge3YFGMB->~+4V2dToH1-wH*S1i3Ys*n8>yTtlgq)J&J0u6Y(BZeZIHs&X<_y zBgfI4x{P1LLcnTaN`zw`liW6{cz%f^4tu337;ZS@xVlAtaZZMQ!2BzM!Kz@1Hoi0? zc@DG>?Jb#EF{D1x&MuY&)#><=3kH#D^wn!B19_JFfkc!o4)pyESigYHc}>%n;l z+P-N!esQS11*Lm?r}dKjE4p|~_h+hjB)e~P%rg4zh9IsTyB&A0emGoPO5;MoH$Ng} zvbXhZN5R-o#-D9!ca&c9qw~8@&%inumwNOWq42==%#hj*oR3w~vhr?PYg_}~buVwu z^shSdnUiVYo6N>HzaxFr)RrjiL09`wKOdG%`4u_z|FP>h(SF=%#7Q5yqExsD5577E zHPGiy9<*Q{bvq7dh9(Q-!?X78WpAH#Ru8(o)+&hajoA9iQfm%&+&WF!G?|*eFGaw$ zx{2%r@RqIUwdvsep5s65rgm>e*7ij8Os#CPLR)iQ>B{$7ap6JD+}a~70hiiDOj`L2 z^=Ifp=fp$N`HhW5~3Y#nXn>Bk9&O6H#$#6e4A6&w<^ugC_$*Y4!S2FI_9xP19s zZ<^O=$>-y1Ub`-qe**f=s~G*jlb6e<7H=(lGtto}Ff()`IBs_^gUB~|8jpB&yUd`L zLsPb3)F$Ot2CD};SA@caTFsRRDx1Yr5$R$KZ|>C(SJcL}jDBy_uFdzeiMfP%v}Omsa<`BwDP6ozF(?iL9O?-@2Qg&Fzd%G@At~N?1ZRSjN0iro z%_A))eG-dDFg@D2A-tsPO3rcCIvH(Z3Bi>m3N7*wBVH-{F{v;?ShIueXjx#TDg?H& zerzBOEXqV$?Uh9khzYS*I`>+ldaR*V$l{(r+dq)0!Hm=!s(Pt5iq#f zzf9zny=qxt^wmMpP3agDX?LxI+C5`;pl|IU1r}!pv?&+F+A52`N3RA?g6aSZT)RdJ zi0@BrgRTKRcD@8%^)rN=n4J15q#X<~Y5^GAKH&CLAT+!0p0=~QE%1A-(;~{3#s^on#I1Q?9=-cCNTmr60pB>2zSeN@8W%n|+)RXt47%1KrnzPGMl1|4 z614-5MsKG};-%)1H@xh4U|TyEOY;nS%Z_IGS^+CBl3f5Kalw@jGg1Enu!oy1bffVI zY0UgQi$7kn`U`-|xOwE#XmvMH>vX_rW;)<2Q}Ej#Yvd&_NCZ2p$Jl z#;LKN0{9%Q0F;2KSlvCSED$+7qF;&7a#d1VF5G@XLi(f{9^rCFrN!HR1jwns!tMB@ zsrtPNw}7E77$!;51RxtVj~rkk8*~Cd9{|S1@dz5~{3(bL3YdvCHxD}?`9_lvX$QvA z6wCK3iA8`f64C6N4w#skq$2bRta$_$0gOF=oN7S>MsErbG~7k+0`=Z9uo9sJTMbm- z-`XKFAL*0fsuzp$ zfH`E82tS}%FXz%#fiWa(3HFBD0PyAu#3TD-0pd+d!`c5Mk!b+E6x0AfuOJgCRD0uj z+JnjJD>PvwKuL^NyCTH+2N&x+3RLHLfU~DV&j8MxiwjNRW+?-TihwXTgnzwHa=MUnHcnZNqE#Od-R zpdyUVpNAMFd5o3P*$&kqwEam=t*A`%x|)6dgTpwJ(D@K(@8yXfcHqoeOZc+P;v+Gu zQSew1lUpe^ubf1~ES9)p=VAUs_BiB1WoF&dpYIl{MnOYBF|8<7+?YBI+^IeEwd z=w@4@F5M5&aW{x!3&E3Fnw$`jHOwj78N)+AcXk?6LM=l;)gfjgK%3LC?*?M@+9b-1 zu`OjktUv^s1P8rYn6cGjDISi*JOz#el|C{gKIiV;;bOxfh1upz1)vxBJx@+bPQ^R6 zI~8(1vrc@v-0Z+Gx9rQWcg!aW==KvY=*b9(9({g2`iw`Lg1XiTl zRUyVhUVgPKek-7p)0BU>W}m`(7yd^~Ce5GyN-%0%YOFY7Y2pA4q}`8IYlXY8N4>}l zh`%ibH$n{XPdjh1C{dXvQMR00>Z0wqC_%Q1clGPcd36kEg3~>?+OZXMQA?w1HRoxT zUuD6_DtqK0w%QrW9yr*v_1%&zQ5n+c_e{~pX|cGyffT|3sL?!hTrlFX0+2hi+R|x# z!Q4hyn+4&?TD}IfU*ipp6x2YC0jJV$A$pbHYVh=@p?whBZNDZ(g{8`QiQQ8#v-qv!b90Npq2eKfvk2Cp5aK}_m>FQ}PNvP|LL54gLF z=BaZm(?!EaZc>9Pla+a#DO}yhFE=O4b09`%e2Ts3?17PC2zc8Q^JI-aFP}!8qTkco z(HoeL#w70ABl-#-K^EvDxVKaE+tTC=4Bl&GCWW4XI{x1b^)6k8+#>1p%j_U^Svcsk zSeb&4+y=|O$oyG08FHnT&ibJJaOJT7(_|3-Ju$M8FOVF#6ro@5tNSgy&Sn+s%DkU{ z0`UbG>9~gDv6!KRcNXa+?WYFZPK(EvtKE^iQu?jp=ljRz%gi`}4EStIQCgi<|kzJAvGr~|Oy7k6L z5ka3XmbU7v;RFG_kL#X*rpDFh-x;Tb&2ahBOZPXE>Hf1;9!+J5TosAk}x3=yd zdmgiu<0b-iED|mFnb*W+ZE$h2L#0t!1ae|C6_*$Fs?C4p3yrwn;=Azfnj^`*aFwL5 z46$c%PfQDOdwBYo0u*?Puu`ne#u&k1otHX(=C1bZoRl^H11@`Y@9K%&eE7F~+i8H# zg$kKmxE7KXy4v;YV>_yVvrLocwfOEs-6~7j5s3aQO$)Dj=Wo!NGK*4c-}T53+}eNe z)8g(BVpwQmQTIIOpI_^n*Qau)_X|!1zQ1DZt$%AbMtA(za!1&2mi85<&#r~F({F=s zyd`j6{M*2Ff${uZZSj)86!>&2hG*SenF_2yS!8n~^_$(gq#+KN&(FHGsx_(!{~)D-I4b|QNGf$36M^yJR20UQ>^;~Ii^VbaG zFt5UrS@dXLWHZV1Or>PXD(T>sK8ZXz9YxIGRk$-Ngo{=GmYY?)WY#gD8d8PI;%2i( zE-6P={a%h3D%hyLaOD}iaOS08u}uE7NeOhtcuYVud*yi9b#@PPOJ-``Wh3IW??2RBaD zz=+IJ=BKn>!h#pkmI&Iln5JewrV<~fLlCghxsss=F7 zpzbRl`@bCg13i4a!vEh6185m9z`+UUa212R5h`~lIMB=Iwx=8>{Enxb&mD|A2o&~g zdgTazkfV}PuT9@ab0!kRD}Na5epp0a1d$=iD&HUzN`Y;doo_p@S=Xc-EJrfZgq>C~~ z0RVJqR5D$K@P9%Jsmkdd*Zoz1+#e7Y0D!1Md!41O;+SMAJq&py5d)2Sn#S ze<#?JVH{&Q&@*v7Bz`X{2SU+l&B%ZY!aHb^8`(C7XV8pp9)Gs!mYBYomR0s7nV9Nq%SS7 zE(^{4EDm&Rrs?T>OnqNhipWH=6XPUeZ|9Xvq#2J?@1cHRA4D}eqc2v*D4M~uLJv_8 z)ql>!0JQAAe@YFZJf5{&ts)=d&h+G)M(`fTlYy|DW&F(vlO?Bb?r)dQV#`KZzT+qO z1?nM_XqYzC>pWeKOc)>tVtgMwW5O`-OREqa8lkqPBBHv^`Q2(*X=S-D?R@EE518I} z*Nl2j=Ltp=Gpv_FcJSwA_U_G?s~30mW&S>#U&1I}{g>m_i}z90w|d zDFL$$BOdiFv!3O|mU}j>U6Ickh5pXcmzPNAUUzDR^dUjQ^;&*`8Hrg2vbwKGGNM*duF?Fi~cBfxeiZNUfA{ZBKr_qaXHPtJ(-H+arN_ZqZS zA}Wu>v*cXattMF1U3Q*B>a^R=JiTfh(Qq$!uoX`4_T}^zilUCtP?vGjI*a`-GoO>P zblsz-Lxat+Xa97Fe4fau;y-;ao_P?sNYeH;ARAbDR?J9SZ_b$}(fuL&HAf8kktaWK zRhKc=)D&c+lg;0MfKuF8X5kg)=>xKgFW~5lo|1dh|9Fw$6w&0j_24;De(;k>Aq$d- zqmq;mQ&e}Cq@n&AJ^iI4=E=Kib8H)!SdyJrdxNiys4$h@v-O zzJ_IwAkBgmrF>$=_`vM1zV4&`%q``{Yvy9 zMZifhVt>11jo8^mW**xw+VGJic|q+f*KTPizL#$10UQ4lI^P7crphGPX!+RAgbPfau){0SRFxL8N6 z{t(%|==a_7GxXj0l8+C1UR~+hA(frr7G*cS@nO7dwukyX+vPJ!fI$?K=oF(aw&1# z6c#ttJ2^X51%QYrc;fILI6NVmK=j5Fyoo!U@Tvk|F@HbkZ-RrFDd}l>|6j22Bl43< zz&I8i$ECB5VzRO`)6z3iG2FbYR7`pnHyMCjfqYpeDYFKTTWNkyN zVMvvcnjwY^yY;51VQ)uchNY83eUd)QKFwj6Dhzf33#Mr9GHJ&})d@hOgp+TQn5X{& DA2&(X literal 0 HcmV?d00001 diff --git a/assets/safari-pinned-tab.svg b/assets/safari-pinned-tab.svg new file mode 100644 index 0000000..f300623 --- /dev/null +++ b/assets/safari-pinned-tab.svg @@ -0,0 +1,38 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + diff --git a/assets/search.js b/assets/search.js new file mode 100644 index 0000000..4ca6af8 --- /dev/null +++ b/assets/search.js @@ -0,0 +1 @@ +window.TEXT_SEARCH_DATA={'posts':[{'title':"一些知识点",'url':"/2015/08/01/knowledges.html"},{'title':"Jekyll的基本用法",'url':"/2015/08/02/jekyll-basic-usage.html"},{'title':"Shell中的字符串操作",'url':"/2015/08/02/shell-string.html"},{'title':"PHP内核探索",'url':"/2015/08/03/init.html"},{'title':"PHP数组的哈希碰撞",'url':"/2015/08/05/Supercolliding-a-PHP-array.html"},{'title':"PHP内核, 开始前的准备",'url':"/2015/08/07/prepare.html"},{'title':"C语言函数的可变参数",'url':"/2015/08/09/varying-number-of-arguments.html"},{'title':"GNU Autotools基础知识",'url':"/2015/08/10/gnu-autotools.html"},{'title':"Python文本处理",'url':"/2015/08/11/python-file-read.html"},{'title':"SQL语句里面的join",'url':"/2015/08/12/sql-join.html"},{'title':"YII2的Behavior总结",'url':"/2015/08/18/YII2-Behavior.html"},{'title':"YII2的Component总结",'url':"/2015/08/18/YII2-component.html"},{'title':"YiiBase小记",'url':"/2015/08/21/Yii2-YiiBase.html"},{'title':"PHP对象传递, 赋值",'url':"/2015/08/21/PHP-object-pass-by-referance.html"},{'title':"PHP数组总结",'url':"/2015/08/22/PHP-array.html"},{'title':"Yii model总结",'url':"/2015/08/25/Yii-Model.html"},{'title':"Yii的Vector和Dictionary",'url':"/2015/08/25/Yii-Vector-Dictionary.html"},{'title':"Javascript函数默认参数",'url':"/2015/09/10/Javascript-default-arguments.html"},{'title':"Javascript 如何判断一个变量的类型",'url':"/2015/09/10/Javascript-variables-type.html"},{'title':"操作文件描述符的属性",'url':"/2015/09/10/C-io-fcntl.html"},{'title':"C libevent异步编程简介",'url':"/2015/09/11/libevent-tiny-introduction.html"},{'title':"Memcached仓库里第一个版本解读",'url':"/2015/09/14/Memcached-earliest-version.html"},{'title':"C语言内存对齐",'url':"/2015/09/17/memory-alignment.html"},{'title':"SQL on duplicate key update简单使用",'url':"/2015/09/17/sql-on-duplicate-key-update.html"},{'title':"如何阅读源代码",'url':"/2015/09/19/how-to-read-source-code.html"},{'title':"APUE笔记 C5 缓冲",'url':"/2015/09/26/APUE-buffer.html"},{'title':"Memcached 的初始化过程",'url':"/2015/10/16/memcached-init.html"},{'title':"Getting started - 创建你的第一个游戏",'url':"/2015/10/17/craftyjs-getting-start.html"},{'title':"Vim as a PHP IDE",'url':"/2015/10/31/vim-as-a-php-ide.html"},{'title':"Memcached套接字的初始化",'url':"/2015/11/04/Memcached-socket-init.html"},{'title':"Memcached的slabs(内存管理)",'url':"/2015/11/05/Memcached-slabs.html"},{'title':"Memcached的事件处理函数",'url':"/2015/11/06/Memcached-event-handler.html"},{'title':"Memcached的数据管理",'url':"/2015/11/07/Memcached-item.html"},{'title':"Memcached的哈希表",'url':"/2015/11/08/Memcached-assoc-xxx.html"},{'title':"Memcached多线程分析",'url':"/2015/11/13/Memcached-threads.html"},{'title':"Memcached线程安全函数列表",'url':"/2015/11/14/Memcached-locks.html"},{'title':"YII2使用Pjax无刷新加载页面",'url':"/2015/11/28/yii2-pjax.html"},{'title':"Yii2 AJAX Form Submission",'url':"/2015/11/28/YII2-AJAX-Form-Submission.html"},{'title':"Convert man page to PDF",'url':"/2015/11/30/convert-man-page-to-pdf.html"},{'title':"setjmp和longjmp函数使用详解",'url':"/2015/12/25/jump-function.html"},{'title':"利用Graphviz Dot绘图",'url':"/2015/12/26/graphviz-dot.html"},{'title':"Python解释器的部分数据结构",'url':"/2015/12/26/python-grammer.html"},{'title':"Yii2学习笔记 -- 开始学习",'url':"/2015/12/30/yii2-getting-start.html"},{'title':"Yii2学习笔记 -- 构建一个自定义的应用",'url':"/2015/12/30/yii2-making-custom-app-with-yii2.html"},{'title':"React基本教程",'url':"/2016/01/06/reactjs-tutorial.html"},{'title':"Makefile 相关",'url':"/2016/04/14/makefile.html"},{'title':"雨天",'url':"/2016/08/16/raining-day.html"},{'title':"MongoDB 学习笔记",'url':"/2017/01/09/learning-mongodb.html"},{'title':"Redux学习笔记",'url':"/2017/02/07/Redux.html"},{'title':"Redux高级用法",'url':"/2017/02/11/Redux-advance.html"},{'title':"Lisp 入门",'url':"/2017/02/26/lisp-basic.html"},{'title':"json c 笔记",'url':"/2017/03/10/json-c.html"},{'title':"Python 官方文档扩展部分笔记",'url':"/2017/05/18/python-official-extension-tutorial.html"},{'title':"比特币科普",'url':"/2018/01/24/bitcoin-note.html"},{'title':"Backtrader 学习系列 - QuickStart",'url':"/2022/07/09/backtrader-quick-start.html"},{'title':"移位寄存器",'url':"/2022/08/13/shift-register.html"},{'title':"如何阅读 Arduino 原理图",'url':"/2022/08/31/arduino-schematic.html"},{'title':"AVR GCC 教程",'url':"/2022/08/31/avr-gcc-tutorial.html"},{'title':"Mac 下 Homebrew 相关使用",'url':"/2022/10/12/homebrew.html"},{'title':"使用 Let's Encrypt 为网站添加 HTTPS 支持",'url':"/2022/10/12/letsencrypt.html"},{'title':"Python 虚拟环境管理 virtualenvwrapper 相关使用",'url':"/2022/10/12/virtualenv.html"},{'title':"硬件/电路/单片机/机器人常用资料",'url':"/2022/10/28/hardware-document-reference-copy.html"},{'title':"使用 Arduino 和 OV7670(不含 FIFO) 拍摄图像",'url':"/2022/10/28/arduino-ov7670.html"},{'title':"sed and awk 101 hacks 笔记",'url':"/2023/03/24/sed-and-awk-101-hacks.html"},{'title':"python 的 metaclass 到底是什么",'url':"/2023/04/14/metaclass.html"},{'title':"python 元编程",'url':"/2023/04/14/meta-programming.html"},{'title':"Monkey Patching in Go",'url':"/2023/06/29/monkey-patching-in-go.html"},{'title':"常见的时间序列趋势判别算法",'url':"/2023/09/15/time-sequence-trending-algorithm.html"},{'title':"用树回归方法画股票趋势线",'url':"/2023/09/17/linear-regression-decision-tree-trending.html"},{'title':"决策树算法",'url':"/2023/09/20/decison-tree.html"},{'title':"使用 Vue 创建 Web Component",'url':"/2024/01/25/vue-web-component.html"},{'title':"机器学习基石 - 作业 2",'url':"/2024/02/27/Machine-Learning-Mathematical-Foundations-hw2.html"}]}; diff --git a/assets/site.webmanifest b/assets/site.webmanifest new file mode 100644 index 0000000..2b120c4 --- /dev/null +++ b/assets/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "TeXt Theme", + "short_name": "TeXt Theme", + "icons": [ + { + "src": "android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..da24e63426828c96f8e147f89da95b39542f07d9 GIT binary patch literal 15086 zcmeI2Yiv|S7=}+epG*T-X zjS@99SZV-KqSYV%@P@4oqVa+n4aQ4Z2w2f7s93Aej?cT@fz5W?-Lu_Ve$1OZJ9FlI zb2;D4oHJ+6dR~^7;{}5rVZhsbj^|zOd0rq8JudLPR?-Rxb$-z>&s&8-2{vT$DnRKG z754=0*LlEszA92EB&l_{r$QHV@k(2*8_Q`d}C71v)c#mq%7N-gbPXC$=}7(d z8N`;UahZ;?$y5$sLIa!t@w%RbkKir%9&Ei^iH`;4kzcbQRo#T)?_kTSBJL+Y=H5tH z2EW1=FaZ)t*Ql{I0;a<+pn5yt189O*Kz>bC>?nATfo=a!#2X<5@@F3Or$SDyhkxN9 z%z{)TA1#N&VC%M_(*5X6jJJW!Z^L%dw4KtIuI7aNCi`xv0YCQu-3K>;tVtbyC~yomOW+649BhSuz+S^{;%CDsSOc5;P8GLNn-E ze}feefcrr0)-~w)Hmu6KmCe%%Cd7o?a^~-3w#WPU^Se`BzO@Hf!e6~GXw0s zBa3(tUIIOvWvA=d2g*B`Qu%X0_X3UEdU!Z`JdLmp93wf}zj zzomKkIcTl+8dShW&^XsxLF@BmIuC{@Ukj~#ix$>Rns-(FnpJ#dws_em###bOg>?b33nF}R-5pOQo?2vUP^wm+cYgu7&ha|6dtUclCRJN zL-mocmKt>|4SlGW*8p4tNB^faRBxfQ!O3~RdBAz#IP}0Jv`J{MPW!FezZ?z8_MNns zrhUqRXat$>ggvky!l3=eHz3(}oN?G_Z+Zbt0F~uO%5xcLU-?0Z-Mi-r=}m@YnumeD z1L-@;TF_p6GRd#ypuOSKqQ=@wT@BjL{uy|JdQ~7FYcIJk>6)Jb+3$jLp+Bh}?T3B> zSHv0Sam)%J;?EAkcF()z9yU35yR-ud_w(Cq6^*u z)nzr7$i1MtcEAi!8|Ak{P!C~P2I)#;axu(<+u$M4yqyc%;crlXjt8qTM3zGvyb7Zs z6BHEo@tmhDGMTN=Adt3#$JXiR&hG5b<0%UYGtW^GFZ;OIVuo-|JJ9vJAe zZXpG(0zI?i*8~@krsuWR8}U+aXgxRyw!waw4zcS&t-IfcB@o+tT5sJ7TKlQ}>go{e z1+BwlHHU*RXzi*pw2rBT%IGmcL|Owh!naTyN$L6j%eBP6g0Eo;Xzix7Qy^aFlc4pP zd~y-21o`I5X!tT=5c-m?B^y+Z)_hZ86iB&H2s-bSArJKIsq!+w+koXg;`NNC19dER O>CKK=$Ie+B8~z81Ji8A7 literal 0 HcmV?d00001 diff --git a/feed.xml b/feed.xml new file mode 100644 index 0000000..82d13d9 --- /dev/null +++ b/feed.xml @@ -0,0 +1,2833 @@ +Jekyll2024-06-27T11:11:10+08:00http://sidgwick.github.io/feed.xml挚爱荒原宋志刚(Sidgwick)的个人博客, 主要是一些技术记录和分享. 不写博客的技术人员不 是好的技术人员. 工作之余写点小博客, 记录学习中遇到的点点滴滴, 方便自己以后 查看, 也方便和我遇到一样问题的人. +Zhigang Song机器学习基石 - 作业 22024-02-27T19:14:41+08:002024-02-27T19:14:41+08:00http://sidgwick.github.io/2024/02/27/Machine-Learning-Mathematical-Foundations-hw2本文可运行的 Jupyter Notebook 链接

+ + + +
import numpy as np
+import math
+from functools import *
+
+ +

Coursera Question 16

+ +

在课堂上, 我们讲授了一维数据的’正负射线’(简单来说就是一维感知器)的学习模型. 该模型包含以下形式的假设:

+ +\[h_{s,\theta}(x) = s \cdot{} sign(x - \theta)\] + +

该模型通常被称为’决策树桩’模型, 是最简单的学习模型之一. 如课堂所示, 对于一维数据, 决策树桩模型的 VC 维为 2.

+ +

事实上, 决策树桩模型是我们可以​通过枚举所有可能的阈值来有效地轻松最小化 $E_{in}$ 的少数模型之一. 特别地, 对于 $N$ 个例子, 最多有 $ 2N $ 个 dichotomy(参见第 5 课幻灯片的第 22 页), 因此最多有 $2N$ 个不同 $E_{in}$ 值. 然后我们可以轻松地选择使得 $E_{in}$ 最小的 dichotomy, 其中可以通过在最小的 $E_{in}$ 中随机选择来消除平局(原文: We can then easily choose the dichotomy that leads to the lowest $E_{in}$, where ties can be broken by randomly choosing among the lowest $E_{in}$ ones). 所选的 dichotomy 表示某些点($\theta$ 范围)和 $s$ 的组合(这里是说 $\theta$ 和 $s$ 的组合确定了一个 dichotomy, 对这个 dichotomy 来说, $\theta$ 的取值本身是一个范围, 在这个范围内, 给定的样本都可以形成同一个 dichotomy), 通常将该范围的中值选作实现 dichotomy 的 $\theta$.

+ +

在本题中, 我们将实现这样的算法, 并在人工数据集上运行程序.

+ +

首先, 通过以下过程生成一维数据:

+ +
    +
  1. 在 $[-1,1]$ 中通过均匀分布生成 $x$
  2. +
  3. 通过 $f(x) = \tilde{s}(x) + noise$ 生成 $y$, 其中 $\tilde{s}(x) = sign(x)$ 并且噪声以 20% 的概率翻转结果.
  4. +
+ +
def h(x, s, theta):
+    t = x - theta
+    return s * (-1 if t < 0 else 1)
+
+ +
def example_data(size):
+    """
+    f(x) = sign(x), flips result with 20% probability
+    """
+
+    def sign(data):
+        return [1 if x > 0 else -1 for x in data]
+
+    def flip(x):
+        if np.random.rand() < 0.2:
+            return -x
+
+        return x
+
+    x = np.random.uniform(-1, 1, size)
+    y = [flip(a) for a in sign(x)]
+
+    res = list(zip(x, y))
+    # np.random.shuffle(res)
+
+    return res
+
+ +

Question 16

+ +

对于任何决策树桩 $h_{s,\theta}$, $\theta \in [-1,1]$, 将 $E_{out}(h_{s,\theta})$ 表示为 $\theta$ 和 $s$ 的函数.

+ +

解:

+ +

考虑作业 2 的第一题, 提到有噪声版的目标函数 $f$ 用下面的形式给出:

+ +\[P(y \mid x) = \begin{cases} + \lambda & y = f(x)\\ + 1 - \lambda & \text{otherwise} + \end{cases}\] + +

假设函数 $h$ 犯错误的概率是 $ \mu $, 那么错误概率可以写成:

+ +\[E_{out} = \lambda \mu + (1-\lambda)(1-\mu)\] + +
+

$E_{in}, E_{out}$ 可以用这种概率形式表示吗?

+ +

在未来课程中, 逻辑斯蒂回归使用的交叉熵错误, 看上去是一个概率形式的表达.

+
+ +

题目中, 已经告诉我们 $\lambda = 1-20\% = 0.8$, 下面试着计算 $\mu$, 情况比较复杂, 对 $s$ 分类讨论(决策树桩的 $s \in {-1, +1} $):

+ +
    +
  1. $s = +1$, $\theta > 0$ 时, $x \in [-1, 1]$ 的点里面, $x \in [0, \theta]$ 部分是分类错误(和无噪声的 $f$ 不一样)的, 因此有 $\mu = \frac{\theta}{2}$
  2. +
  3. $s = +1$, $\theta < 0$ 时, 参考 1 的分析, 有 $\mu = \frac{\lvert \theta \rvert}{2}$
  4. +
  5. $s = -1$, $\theta > 0$ 时, 参考 1 的分析, 有 $\mu = 1 - \frac{\theta}{2}$
  6. +
  7. $s = -1$, $\theta < 0$ 时, 参考 1 的分析, 有 $\mu = 1 - \frac{\lvert \theta \rvert}{2}$
  8. +
+ +

把上面四种情况写在一起(注意这不是对概率在求和, 只是将上面四种情况用一个式子表达了出来而已):

+ +\[\begin{align*} +\mu &= \frac{1+s}{2} \times \frac{\lvert \theta \rvert}{2} + \frac{1-s}{2} \times (1 - \frac{\lvert \theta \rvert}{2}) \\ +&= \frac{1}{2}(s \lvert \theta \rvert - s + 1) +\end{align*}\] + +

$E_{out}$ 里面代入 $\lambda$ 和 $\mu$, 可以得到 $E_{out} = 0.5 + 0.3s(\lvert \theta \rvert - 1)$

+ +

Coursera Question 17

+ +

Question 17

+ +

按照上述过程生成大小为 20 的数据集, 并在数据集上运行一维决策树桩算法. 记录 $E_{in}$ 并使用上述公式计算 $E_{out}$, 重复该实验(包括数据生成, 运行决策树桩算法以及计算 $E_{in}$ 和 $E_{out}$) 5000 次. $E_{in}$ 的平均值是多少?

+ +

解:

+ +

按照题目的说法, 我们要暴力遍历所有的 dichotomy, 因此先准备好能决定 dichotomy 的 $(\theta, s)$ 集合. 如果 \(x \in \{ x_1, x_2, ... x_{20} \mid x_1 < x_2 < ... < x_{20} \}\), 那么 $\theta$ 可以取的值有(使用每个 dichotomy $\theta$ 范围的中值) $ \frac{-1 + x_1}{2}, \frac{x_1 + x_2}{2}, …, \frac{x_{19} + x_{20}}{2}, \frac{x_{20} + 1}{2} $, 共计 21 个点.

+ +

至于 $s$, 它仅可以取 ${-1, +1}$.

+ +
+

出于计算后面的 19/20 题通用考虑, 下面的代码里面对第一个和最后一个 $\theta$ 的处理, 不是用的 $-1$ 和 $+1$ 边界, 而是用的 $ x_0 - 1$ 和 $ x_{20} + 1 $.

+
+ +
def get_theta_list(data):
+    result = []
+
+    xs = sorted([x[0] for x in data])
+
+    prev = xs[0] - 1
+    for x in xs:
+        result.append((prev + x) / 2)
+        prev = x
+
+    result.append((prev + prev + 1) / 2)
+    
+    return result
+
+def get_dichotomy_theta_and_s(data):
+    result = []
+
+    theta_list = get_theta_list(data)
+    
+    for theta in theta_list:
+        result.append((theta, -1))
+        result.append((theta, +1))
+
+    return result
+
+ +

有了 $(\theta, s)$ 的集合之后, 所要做的事情就是遍历这个集合, 并计算在样本点上的 $E_{in}$, 然后用上面的公式可以算 $E_{out}$ 出来.

+ +
def decision_stump(data):
+    dichotomy_list = get_dichotomy_theta_and_s(data)
+
+    best = (0, 0)
+    best_in = np.inf
+    best_out = np.inf
+    
+    for theta, s in dichotomy_list:
+        _h = partial(h, s=s, theta=theta)
+        
+        # 代入数据运算
+        err = 0
+        for x, y in data:
+            err += _h(x) == y
+
+        if err < best_in:
+            best_in = err
+            best = (theta, s)
+
+        err_out = 0.5 + 0.3*s*(np.abs(theta) - 1)
+        if err_out < best_out:
+            best_out = err_out
+            # print(s, theta, err_out)
+
+    return best_in / len(data), best_out, best
+
+ +
loops = 5000
+
+err_in = 0
+err_out = 0
+
+for i in range(loops):
+    data = example_data(size=20)
+    e_in, e_out, _ = decision_stump(data)
+    
+    err_in += e_in
+    err_out += e_out
+
+print("err_in", err_in / loops)
+print("err_out", err_out / loops)
+
+ +

代码输出:

+ +
err_in 0.16993999999999992
+err_out 0.21064865779191114
+
+ +

Coursera Question 18

+ +

Question 18

+ +

解:

+ +

参考 Question 17 里面关于 $E_{out}$ 的计算.

+ +

疑问❓❓❓

+ +

我最开始的更新错误部分的写法实际上是:

+ +
if err < best_in:
+    best_in = err
+    best_out = 0.5 + 0.3*s*(np.abs(theta) - 1)
+
+ +

也就是只有在遇到更好的 err_in 的时候, 才用它的 $s, \theta$ 来更新一下 best_out, 但是这样算出来 $E_{out}$ 均值达到了 $0.7$

+ +
+

!!!

+ +

这里为什么要在无论何种场景下都要更新 $E_{out}$ 呢?

+
+ +

Coursera Question 19

+ +

Question 19

+ +

决策树桩也适用于多维数据. 特别地, 现在每个决策树桩处理一个特定的维度 $i$, 如下:

+ +\[h_{s,i,\theta}(x) = s \cdot{} sign(x_i - \theta)\] + +

对多维数据实现以下决策树桩算法:

+ +
    +
  1. 对于每个维度 $i = 1, 2, … , d$, 使用你刚刚实现的一维决策桩算法找到最佳决策桩 $h_{s,i, \theta}$
  2. +
  3. 返回以 $E_{in}$ 而言 ‘最佳中的最佳’ 决策桩. 如果出现平局, 请从 $E_{in}$ 最小的桩中随机选择一个(原文: return the “best of best” decision stump in terms of $E_{in}$. If there is a tie, please randomly choose among the lowest-$E_{in}$ ones, 原文读的有点稀里糊涂, 这意思是不是说计算好 N 个维度之后, 返回 N 个维度最小的 $E_{in}$ 里面的那个最小的? 从网上别人的代码看, 好像是要这么干)
  4. +
+ +

训练数据 $\mathcal{D}_{train}$ 可在以下位置获得: https://www.csie.ntu.edu.tw/~htlin/mooc/datasets/mlfound_math/hw2_train.dat

+ +

测试数据 $\mathcal{D}_{test}$ 可在以下位置获得: https://www.csie.ntu.edu.tw/~htlin/mooc/datasets/mlfound_math/hw2_test.dat

+ +

对 \(\mathcal{D}_{train}\) 运行该算法, 报告您的程序返回的最佳决策桩的 \(E_{in}\)

+ +

解:

+ +

读取并处理原始数据, 然后送到 Question 17 实现的算法里面计算即可.

+ +
def read_data(file):
+    result = []
+
+    fh = open(file)
+    for line in fh.readlines():
+        parts = line.strip().split(" ")
+
+        x = [float(x) for x in parts[:-2]]
+        y = int(parts[-1])
+
+        result.append((x, y))
+
+    return result
+
+def train(_data):
+    dim = len(_data[0][0])
+
+    params = []
+    best_in = np.inf
+    
+    for i in range(dim):
+        data = [(x[i], y) for x, y in _data]
+        err_in, err_out, best = decision_stump(data)
+        
+        # 记一下这维度上表现最好的 theta 和 s, 以后预测可以用
+        params.append(best)
+
+        if err_in < best_in:
+            best_in = err_in
+
+    return best_in, params
+
+train_dat = read_data("hw2_train.dat")
+err_in, params = train(train_dat)
+
+print(err_in)
+
+ +

代码输出:

+ +
0.25
+
+ +

Coursera Question 20

+ +

Question 20

+ +

用第 19 题求得的 params, 在 $\mathcal{D}_{test}$ 上面执行预测, 并收集每个维度上的错误情况, 最后报告一个最小的错误.

+ +
def test(params):
+    _data = read_data("hw2_test.dat")
+
+    err_out = []
+    for i, (theta, s) in enumerate(params):
+        _h = partial(h, s=s, theta=theta)
+        
+        # 预测测试集数据
+        data = [(x[i], y) for x, y in _data]
+
+        err = 0
+        for x, y in data:
+            err += _h(x) == y
+
+        err_out.append(err)
+
+    return np.array(err_out) / len(_data)
+
+res = test(params)
+np.min(res)
+
+ +

代码输出:

+ +
0.355
+
]]>
Zhigang Song
使用 Vue 创建 Web Component2024-01-25T03:00:00+08:002024-01-25T03:00:00+08:00http://sidgwick.github.io/2024/01/25/vue-web-component准备 + +

安装 vue 命令行工具:

+ +
> yarn global add @vue/cli
+
+ +

生成一个空白仓库:

+ +
> vue create json-viewer
+
+ +

可以使用的选项如下(参考, 根据自己的需要调整):

+ + + +
Vue CLI v5.0.8
+? Please pick a preset: Manually select features
+? Check the features needed for your project: Babel, TS, CSS Pre-processors, Linter
+? Choose a version of Vue.js that you want to start the project with 2.x
+? Use class-style component syntax? No
+? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? No
+? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with dart-sass)
+? Pick a linter / formatter config: Prettier
+? Pick additional lint features: Lint on save
+? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
+? Save this as a preset for future projects? No
+
+ +

开发

+ +

功能开发

+ +

功能开发可以像普通的 vue 应用一样进行.

+ +

删除 HelloWorld 组件, 并创建一个叫做 TwoCounter 的组件, 这个组件引用另一个叫做 BasicCounter 的组件.

+ +

两个文件内容分别如下:

+ +

BasicCounter.vue

+ +
<template>
+  <div class="basic-counter">
+    <h1>Counter - {{ index }}</h1>
+    <div class="counter">
+      <p>Cuttent Value is {{ counter }}</p>
+      <button @click="increment">Increment</button>
+      <button @click="decrement">Decrement</button>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import Vue from "vue";
+
+export default Vue.extend({
+  name: "BasicCounter",
+  props: {
+    index: String,
+  },
+  data: function () {
+    return {
+      counter: 0,
+    };
+  },
+  methods: {
+    increment: function () {
+      this.counter = this.counter + 1;
+    },
+    decrement: function () {
+      this.counter = this.counter - 1;
+    },
+  },
+});
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped lang="scss">
+.basic-counter {
+  display: flex;
+  flex-direction: column;
+
+  h3 {
+    margin: 40px 0 0;
+  }
+  .counter {
+  }
+}
+</style>
+
+ +

TwoCounter.vue

+ +
<template>
+  <div class="two-counter">
+    <h1>{{ title }}</h1>
+    <div class="counters">
+      <BasicCounter index="1" />
+      <BasicCounter index="2" />
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import Vue from "vue";
+import BasicCounter from "./BasicCounter.vue";
+
+export default Vue.extend({
+  name: "TwoCounter",
+  components: {
+    BasicCounter,
+  },
+  props: {
+    title: String,
+  },
+});
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped lang="scss">
+.two-counter {
+  width: 800px;
+  .counters {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-around;
+  }
+}
+</style>
+
+ +

写好之后, 使用以下命令编译成 Web Component:

+ +
> yarn run build -- --target wc --name two-counter --inline-vue src/components/TwoCounter.vue
+
+ +

上面的命令会在 dist 里面生成一个叫做 two-counter.js 的文件, 可以直接在 html 里面引用它:

+ +
<!DOCTYPE html>
+<html lang="">
+  <head>
+    <meta charset="utf-8" />
+    <title>two-counter</title>
+    <script src="./two-counter.js"></script>
+  </head>
+  <body>
+    <two-counter></two-counter>
+  </body>
+</html>
+
+ +

最后的运行效果如下:

+ +

+ +

Vue 3

+ +

如果你用的是 Vue 3, 目前它对 Web Component 的支持有限, 需要使用一种变通的方式来生成.

+ +

创建一个包装性质的代码片段:

+ +
import { createApp } from "vue";
+
+import App from "./App.vue";
+
+class CustomElement extends HTMLElement {
+  constructor() {
+    super();
+  }
+
+  connectedCallback() {
+    const options = typeof App === "function" ? App.options : App;
+    const propsList = Array.isArray(options.props)
+      ? options.props
+      : Object.keys(options.props || {});
+
+    const props = {};
+    // Validate, if all props are present
+    for (const prop of propsList) {
+      const propValue =
+        process.env.NODE_ENV === "development"
+          ? process.env[`VUE_APP_${prop.toUpperCase()}`]
+          : this.attributes.getNamedItem(prop)?.value;
+
+      if (!propValue) {
+        console.error(`Missing attribute ${prop}`);
+        return;
+      }
+
+      props[prop] = propValue;
+    }
+
+    const app = createApp(App, props);
+
+    const wrapper = document.createElement("div");
+    app.mount(wrapper);
+
+    this.appendChild(wrapper.children[0]);
+  }
+}
+
+window.customElements.define("two-counter", CustomElement);
+
+ +
> yarn run build -- --target lib --name two-counter --inline-vue src/two-counter.js
+
]]>
Zhigang Song
决策树算法2023-09-20T05:14:41+08:002023-09-20T05:14:41+08:00http://sidgwick.github.io/2023/09/20/decison-tree算法原理 + +

树模型

+ +

决策树根节点开始一步步走到叶子节点(决策), 所有的数据最终都会落到叶子节点, 利用决策树既可以做分类也可以做回归.

+ +

树的组成

+ +
    +
  • 根节点: 第一个选择点
  • +
  • 非叶子节点与分支: 中间过程
  • +
  • 叶子节点: 最终的决策结果
  • +
+ +

决策树的训练是从给定的训练集构造(从根节点开始选择特征, 如何进行特征切分)出来一棵树, 然后将测试数据根据构造出来的树模型从上到下去走一遍, 得到决策结果.

+ +

一旦构造好了决策树, 那么分类或者预测任务就很简单了, 只需跟着树走一遍决策流程就可以了, 那么难点就在于如何构造出来一颗树.

+ + + +

熵的作用

+ +

根节点的选择该用哪个特征呢? 我们的目标应该是经过根节点的决策之后尽可能好的切分数据(即决策后分类的效果更好), 然后再找除了根节点用到的特征之外的其他特征中切分效果最好的特征作为决策树的后续节点, 然后是第三好, 第四好…

+ +

所以我们需要一种衡量标准, 来计算通过不同特征进行分支选择后的分类 情况, 找出来最好的那个当成根节点.

+ +

+ +

熵是表示随机变量不确定性的度量, 它的定义如下:

+ +\[H(X) = -\sum_{i=1}^{n} p_{i}\log{p_{i}}\] + +

举例有 $A = \lbrace 1,1,1,1,1,1,1,1,2,2 \rbrace$, $B=\lbrace 1,2,3,4,5,6,7,8,9,1 \rbrace$ 两个集合, 显然 $A$ 集合的熵值要低,因为 $A$ 里面只有两种类别, 相对稳定一些. 而 $B$ 中类别太多了, 熵值就会大很多. (在分类任务中我们希望通过节点分支后数据类别的熵值大还是小呢?)

+ +

信息增益原理

+ +

集合中的信息越混乱, 得到的熵值也就越大. 如当 $p=0$ 或 $p=1$ 时, $H(p)=0$, 随机变量完全没有不确定性. 当 $p=0.5$ 时, $H(p)=1$, 此时随机变量的不确定性最大.

+ +

信息增益

+ +

指的是在经过特征 $X$ 的分类之后, 使得结果熵相对于分类操作之前的熵的减小值.

+ +

决策树构造实例

+ +

现在有一个表, 其中数据列举了人的幸福与否和年龄,工作,家庭,贷款情况的关系, 我们基于这张表构建一个决策树.

+ +

首先我们可以计算得到, 根节点的信息熵为(6 个 no, 9 个 yes):

+ +\[\begin{aligned} +H ~=~& - \frac{6}{15} \times \log{\frac{6}{15}} - \frac{9}{15} \times \log{\frac{9}{15}} \\ + \approx ~& 0.97095 +\end{aligned}\] + +

然后对 4 个特征逐一分析, 分别尝试用他们作为决策依据, 看一下决策后那个特征对应的信息增益最大, 就可以认定这个特征是最好的分类特征. 要注意的是, 用特征切分原始数据集之后, 切分结果对应的信息熵是一个加权平均熵.

+ +

我们以 F3-HOME 特征分类来讲解. 它可以把数据分类为 2 个集合(这个特征有两个特征值).

+ +

在特征值为 0 的情况下, 有 6 个 no 值, 3 个 yes 值. +在特征值为 1 的情况下, 有 0 个 no 值, 6 个 yes 值.

+ +\[\begin{aligned} +H_{3} ~=~& \frac{9}{15} \times (-\frac{6}{9} \times \log{\frac{6}{9}} - \frac{3}{9} \times \log{\frac{3}{9}}) + \frac{6}{15} \times (-\frac{0}{6} \times \log{\frac{0}{6}} - \frac{6}{6} \times \log{\frac{6}{6}}) \\ + \approx ~& 0.55097 +\end{aligned}\] + +

用同样的办法, 可以求出来其他切分方式的熵值:

+ +\[\begin{aligned} +H_1 \approx ~& 0.88794 \\ +H_2 \approx ~& 0.64730 \\ +H_4 \approx ~& 0.60796 +\end{aligned}\] + +

可以看到使用 F3-HOME 特征, 信息增益是最大的, 因此可以选择它作为根节点.

+ +

接下来对于各个特征子树的节点, 构建下一层的决策树时候就是在子数据集合基础上, 再找最优特征作为分类依据. 更深一层的节点也是一样的原理.

+ +
from typing import Union
+
+import math
+import json
+
+
+# 创建数据
+def createDataSet():
+    # 数据
+    dataSet = [
+        [0, 0, 0, 0, "no"],
+        [0, 0, 0, 1, "no"],
+        [0, 1, 0, 1, "yes"],
+        [0, 1, 1, 0, "yes"],
+        [0, 0, 0, 0, "no"],
+        [1, 0, 0, 0, "no"],
+        [1, 0, 0, 1, "no"],
+        [1, 1, 1, 1, "yes"],
+        [1, 0, 1, 2, "yes"],
+        [1, 0, 1, 2, "yes"],
+        [2, 0, 1, 2, "yes"],
+        [2, 0, 1, 1, "yes"],
+        [2, 1, 0, 1, "yes"],
+        [2, 1, 0, 2, "yes"],
+        [2, 0, 0, 0, "no"],
+    ]
+
+    # 列名
+    features = ["F1-AGE", "F2-WORK", "F3-HOME", "F4-LOAN"]
+    return dataSet, features
+
+
+def maxDataSetLabel(dsLabelList):
+    """找到 dsLabelList 中元素的众数并返回"""
+    counter = {}
+
+    for i in dsLabelList:
+        cnt = counter.get(i, 0)
+        cnt += 1
+        counter[i] = cnt
+
+    key = max(counter, key=counter.get)
+    return key
+
+
+def dataSetEntropy(dataSet) -> float:
+    """计算 dataSet 的熵值"""
+    counter = {}
+    for row in dataSet:
+        label = row[-1]
+        cnt = counter.get(label, 0) + 1
+        counter[label] = cnt
+
+    result = 0
+    total = len(dataSet)
+
+    for val in counter.values():
+        prob = val / total
+        result -= prob * math.log(prob, 2)
+
+    return result
+
+
+def weightSubDataSetEntropy(subDataSet, dataSetLen) -> float:
+    """计算按照特征拆分好的子数据集的加权熵"""
+    entropy = 0
+    dataSetLen = float(dataSetLen)
+    for _dataSet in subDataSet.values():
+        _entropy = dataSetEntropy(_dataSet)
+        entropy += _entropy * len(_dataSet) / dataSetLen
+
+    return entropy
+
+
+def chooseBestFeatureToSplit(dataSet):
+    """计算信息增益最大的特征
+
+    1. 计算分裂之前的熵值
+    2. 找到可以用于分裂的特征列表
+    3. 尝试使用 2 里面的每个特征分裂构造子树, 然后判断那个信息增益最大
+    4. 使用信息增益最大的那个特征, 构造分类节点数据
+    """
+    dsLen = len(dataSet)
+    numberFeatures = len(dataSet[0]) - 1  # number of features
+    curEntropy = dataSetEntropy(dataSet)  # initial entropy
+
+    bestGain = -1
+    bestFeatIndex = -1
+    for feat in range(numberFeatures):
+        subDataSet = subDataSetByFeature(dataSet, feat)
+        entropy = weightSubDataSetEntropy(subDataSet, dsLen)
+
+        gain = curEntropy - entropy
+        if gain > bestGain:
+            bestGain = gain
+            bestFeatIndex = feat
+
+    return bestFeatIndex
+
+
+def subDataSetByFeature(dataSet, bestFeatIndex):
+    """按照 bestFeatIndex 指示的特征, 将 dataSet 分类"""
+    result = {}
+
+    for row in dataSet:
+        val = row[bestFeatIndex]
+
+        valList = result.get(val, [])
+        valList.append(row[:bestFeatIndex] + row[bestFeatIndex + 1 :])
+        result[val] = valList
+
+    return result
+
+
+def createTreeNode(dataSet, labelList) -> Union[str, dict]:
+    """
+    创建决策树
+    :param dataSet: 数据集
+    :param features: 特征列表
+    :return: 决策树
+    """
+    # 当前数据集合中, 标签数据
+    dsLabelList = [i[-1] for i in dataSet]
+
+    # 已经分好类别, 节点不需要再分裂了
+    if len(set(dsLabelList)) <= 1:
+        return dsLabelList[0]
+
+    # label 虽然还没有完全分开, 但是已经没有特征可供拆分了, 统计众数, 作为标签返回
+    if len(labelList) <= 1:
+        return maxDataSetLabel(dsLabelList)
+
+    # 找到最优的特征用于分裂
+    bestFeatIndex = chooseBestFeatureToSplit(dataSet)
+    bestFeat = labelList[bestFeatIndex]
+
+    # 删除被选择的特征
+    del labelList[bestFeatIndex]
+
+    # 按照特征, 将数据分成不同的子集
+    subDataSet = subDataSetByFeature(dataSet, bestFeatIndex)
+
+    # 遍历每个特征值, 然后递归的构造子树
+    node = {}
+    for featVal, _dataSet in subDataSet.items():
+        _labelList = labelList.copy()
+        node[featVal] = createTreeNode(_dataSet, _labelList)
+
+    # 创建树
+    tree = {bestFeat: node}
+    return tree
+
+
+dataSet, features = createDataSet()
+
+tree = createTreeNode(dataSet, features)
+print(json.dumps(tree))
+
+ +

信息增益率与 gini 系数

+ +
决策树算法
+ID3
+信息增益(有什么问题呢?)
+
+问题:ID当做特征,熵值为0,不适合解决稀疏特征,种类非常多的。
+
+C4.5
+信息增益率(解决ID3问题,考虑自身熵)
+
+CART
+使用GINI系数来当做衡量标准
+
+GINI系数
+\large Gini(p)=\sum_{k=1}^{K}p_{k}(1-p_{k})=1-\sum_{k=1}^{K}p_{k}^{2}
+
+(和熵的衡量标准类似,计算方式不相同)
+
+连续值
+进行离散化。
+
+
+
+六、预剪枝方法
+决策树剪枝策略
+为什么要剪枝
+决策树过拟合风险很大,理论上可以完全分得开数据 (想象一下,如果树足够庞大,每个叶子节点不就一个数据了嘛)
+
+剪枝策略
+(预剪枝,后减枝)
+
+预剪枝
+边建立决策树过程中进行剪枝的操作(更实用)。
+
+限制深度,叶子节点个数。叶子节点样本数,信息增益量等。
+
+七、后剪枝方法
+后剪枝:当建立完决策树后来进行剪枝操作。
+
+通过一定的衡量标准\large C_{a}(T)=C(T)+\alpha \left | T_{leaf} \right |
+
+ \large C_{a}(T):损失
+
+\large C(T):gini系数
+
+\large T_{leaf}:叶子节点个数
+
+(叶子节点越多,损失越大)
+
+
+
+八、回归问题解决
+回归问题将方差作为衡量(评估)标准。看标签的平均方差。
+
+分类问题将熵值作为衡量标准。
+
+ +

参考资料

+ +]]>
Zhigang Song
用树回归方法画股票趋势线2023-09-17T05:34:04+08:002023-09-17T05:34:04+08:00http://sidgwick.github.io/2023/09/17/linear-regression-decision-tree-trending原文: 机器学习_用树回归方法画股票趋势线

+ +

本篇的主题是分段线性拟合, 也叫回归树, 是一种集成算法, 它同时使用了决策和线性回归的原理, 其中有两点不太容易理解, 一个是决策树中熵的概念, 一个是线性拟合时求参数的公式为什么是由矩阵乘法实现的. 如需详解, 请见前篇:

+ + + +

画出股票的趋势线

+ +

我们常在股票节目里看到这样的趋势线:

+ +

趋势线

+ + + +

比如说平台突破就可以买入了, 几千支股票, 能不能用程序的方式筛选哪支突破了呢? 需要解决的主要问题是: 怎么判断一段时间内股票的涨/跌/横盘, 以及一段趋势的起止点和角度呢?

+ +

分段线性拟合

+ +

这里我们使用分段线性拟合, 图中蓝色的点是某支股票每日的收盘价, 红色的直线为程序画出的趋势线. 稍做修改, 还可以轻松地画出每段趋势所在的箱体, 阻力线和支撑线, 以及判断此前一般时间的趋势. 下面我们就来看看原理和具体算法.

+ +

相关算法

+ +

线性回归

+ +

先看看线性回归(Linear regression), 线性回归是利用数理统计中回归分析, 来确定两种或两种以上变量间相互依赖的定量关系的一种统计分析方法. 简单地说, 二维中就是画一条直线, 让它离所有点都尽量地近(距离之和最小), 用线抽象地表达这些点. 具体请见《机器学习_最小二乘法,线性回归与逻辑回归》

+ +

线性回归

+ +

决策树

+ +

我们再看看决策树, 决策树(Decision Tree)决策树是一个预测模型: 它是通过一系列的判断达到决策的方法. 具体请见《机器学习_决策树与信息熵》.

+ +

决策树

+ +

树回归

+ +

树回归把决策树和线性回归集成在一起, 先决策树, 在每个叶节点上构建一个线性方程. 比如说数据的最佳拟合是一条折线, 那就把它切成几段用线性拟合, 每段切多长呢? 我们定义一个步长(以忽略小的波动, 更好地控制周期), 在整个区域上遍历, 找最合适的点(树的分叉点), 用该点切分成两段后, 分别线性拟合, 取整体误差和最小的点. 以此类拟, 再分到三段, 四段…, 为避免过拟合, 具体实现一般同时使用前剪枝和后剪枝.

+ +

代码

+ +
# -*- coding: utf-8 -*-
+
+import tushare as ts
+import pandas as pd
+import numpy as np
+import matplotlib.pyplot as plt
+
+
+# 用feature把dataSet按value分成两个子集
+def binSplitDataSet(dataSet, feature, value):
+    mat0 = dataSet[np.nonzero(dataSet[:, feature] > value)[0], :]
+    mat1 = dataSet[np.nonzero(dataSet[:, feature] <= value)[0], :]
+    return mat0, mat1
+
+
+# 求给定数据集的线性方程
+def linearSolve(dataSet):
+    m, n = np.shape(dataSet)
+    X = np.mat(np.ones((m, n)))
+    # 第一行补1,线性拟合要求
+    Y = np.mat(np.ones((m, 1)))
+    X[:, 1:n] = dataSet[:, 0 : n - 1]
+    Y = dataSet[:, -1]  # 数据最后一列是y
+    xTx = X.T * X
+    if np.linalg.det(xTx) == 0.0:
+        raise NameError(
+            "This matrix is singular, cannot do inverse,\n\
+        try increasing dur"
+        )
+    ws = xTx.I * (X.T * Y)  # 公式推导较难理解
+    return ws, X, Y
+
+
+# 求线性方程的参数
+def modelLeaf(dataSet):
+    ws, X, Y = linearSolve(dataSet)
+    return ws
+
+
+# 预测值和y的方差
+def modelErr(dataSet):
+    ws, X, Y = linearSolve(dataSet)
+    yHat = X * ws
+    return sum(np.power(Y - yHat, 2))
+
+
+def chooseBestSplit(dataSet, rate, dur):
+    # 判断所有样本是否为同一分类
+    if len(set(dataSet[:, -1].T.tolist()[0])) == 1:
+        return None, modelLeaf(dataSet)
+    m, n = np.shape(dataSet)
+    S = modelErr(dataSet)  # 整体误差
+    bestS = np.inf
+    bestIndex = 0
+    bestValue = 0
+    for featIndex in range(n - 1):  # 遍历所有特征, 此处只有一个
+        # 遍历特征中每种取值
+        for splitVal in set(dataSet[:, featIndex].T.tolist()[0]):
+            mat0, mat1 = binSplitDataSet(dataSet, featIndex, splitVal)
+            if (np.shape(mat0)[0] < dur) or (np.shape(mat1)[0] < dur):
+                continue  # 样本数太少, 前剪枝
+            newS = modelErr(mat0) + modelErr(mat1)  # 计算整体误差
+            if newS < bestS:
+                bestIndex = featIndex
+                bestValue = splitVal
+                bestS = newS
+    if (S - bestS) < rate:  # 如差误差下降得太少,则不切分
+        return None, modelLeaf(dataSet)
+    mat0, mat1 = binSplitDataSet(dataSet, bestIndex, bestValue)
+    return bestIndex, bestValue
+
+
+def isTree(obj):
+    return type(obj).__name__ == "dict"
+
+
+# 预测函数,数据乘模型,模型是斜率和截距的矩阵
+def modelTreeEval(model, inDat):
+    n = np.shape(inDat)[1]
+    X = np.mat(np.ones((1, n + 1)))
+    X[:, 1 : n + 1] = inDat
+    return float(X * model)
+
+
+# 预测函数
+def treeForeCast(tree, inData):
+    if not isTree(tree):
+        return modelTreeEval(tree, inData)
+    if inData[tree["spInd"]] > tree["spVal"]:
+        if isTree(tree["left"]):
+            return treeForeCast(tree["left"], inData)
+        else:
+            return modelTreeEval(tree["left"], inData)
+    else:
+        if isTree(tree["right"]):
+            return treeForeCast(tree["right"], inData)
+        else:
+            return modelTreeEval(tree["right"], inData)
+
+
+# 对测试数据集预测一系列结果, 用于做图
+def createForeCast(tree, testData):
+    m = len(testData)
+    yHat = np.mat(np.zeros((m, 1)))
+    for i in range(m):  # m是item个数
+        yHat[i, 0] = treeForeCast(tree, np.mat(testData[i]))
+    return yHat
+
+
+# 绘图
+def draw(dataSet, tree):
+    plt.scatter(dataSet[:, 0], dataSet[:, 1], s=5)  # 在图中以点画收盘价
+    yHat = createForeCast(tree, dataSet[:, 0])
+    plt.plot(dataSet[:, 0], yHat, linewidth=2.0, color="red")
+    plt.show()
+
+
+# 生成回归树, dataSet是数据, rate是误差下降, dur是叶节点的最小样本数
+def createTree(dataSet, rate, dur):
+    # 寻找最佳划分点, feat为切分点, val为值
+    feat, val = chooseBestSplit(dataSet, rate, dur)
+    if feat == None:
+        return val  # 不再可分
+    retTree = {}
+    retTree["spInd"] = feat
+    retTree["spVal"] = val
+    lSet, rSet = binSplitDataSet(dataSet, feat, val)  # 把数据切给左右两树
+    retTree["left"] = createTree(lSet, rate, dur)
+    retTree["right"] = createTree(rSet, rate, dur)
+    return retTree
+
+
+if __name__ == "__main__":
+    df = ts.get_k_data(code="002230", start="2017-01-01")  # 科大讯飞今年的股票数据
+    e = pd.DataFrame()
+    e["idx"] = df.index  # 用索引号保证顺序X轴
+    e["close"] = df["close"]  # 用收盘价作为分类标准Y轴, 以Y轴高低划分X成段,并分段拟合
+    arr = np.array(e)
+    tree = createTree(np.mat(arr), 100, 10)
+draw(arr, tree)
+
+ +

分析

+ +

算法的拟合度和复杂度是使用步长, 误差下降和最小样本数控制的, 计算的时间跨度(一月/一年)也影响着程序运行时间.

+ +

例程中计算的是 科大讯飞 近一年来的股价趋势, 计算用时约十秒左右(我的机器速度还可以).

+ +

要计算所有的股票, 也需要不少时间. 所以具体实现时, 一方面可以利用当前价格和移动均线的相对位置过滤掉一些股票, 另一方面, 也可以将计算结果存储下来, 以避免对之前数据的重复计算.

]]>
Zhigang Song
常见的时间序列趋势判别算法2023-09-15T19:34:04+08:002023-09-15T19:34:04+08:00http://sidgwick.github.io/2023/09/15/time-sequence-trending-algorithm本文介绍常见的时间序列趋势判别算法:

+ +
    +
  1. 多项式拟合(斜率)
  2. +
  3. Mann-Kendall 趋势检验检验
  4. +
  5. Cox-stuart 趋势检验
  6. +
+ +

多项式拟合(最小二乘法)

+ +

基本原理

+ +

核心是使用最小二乘法见序列拟合成一条直线, 然后根据直线的斜率 k 判断序列的走势. 如果返回的是正数则正增长, 如果返回的是负数则为下降, 如果为 0 则表示没有趋势.

+ + + +

本方法的优点是方法简单, 可解释性强. 缺点是要求趋势是线性的, 当数去波动较大时, 无法准确拟合.

+ +
def trendline(data):
+    order = 1
+    index = [i for i in range(1, len(data) + 1)]
+    coeffs = np.polyfit(index, list(data), order)
+    slope = coeffs[-2]
+    return float(slope)
+
+
+resultent = trendline(List)
+print(resultent)
+
+ +

该方法主要用到的函数是 np.polyfit(x, y, deg, *args), 其中:

+ +
    +
  1. deg 为需要拟合函数的最高次数, 当 deg = 0 时, 式子是一个常数项, 即 $y = a_0$
  2. +
  3. np.polyfit 函数的返回值是拟合好之后的参数, 即 $a_n, a_{n-1}, …, a_0$
  4. +
+ +

np.polyfit 的返回结果可以传递给 np.ploy1d, 这个函数会生成对应的多项式表达式.

+ +

+ +

实验

+ +
import numpy as np
+from matplotlib import pyplot as plt
+
+def func(x):
+    '''原函数'''
+    return 2*x*4 - 2*x**3 - 5*x**2 - x + 1
+
+def trendline(x, y, n):
+    '''拟合函数,输出参数'''
+    model = np.polyfit(x, y, deg=n)
+    return np.poly1d(model)
+
+
+# 生成 30 个时序坐标点 (x, y),前 20 个点用于拟合,后 10 个点用于预测
+x = np.linspace(-3, 3, 30)
+y = func(x)
+y1 = y + np.random.randn(30) * 1.5 # 加上噪声
+
+ff = trendline(x[:20], y1[:20], n=4)
+pred = np.poly1d(ff)(x)
+
+# 作图
+plt.figure(figsize=(15, 7))
+plt.scatter(x, y, color='blue', label="raw")
+plt.plot(x, y1, color='yellow', label='real')
+plt.plot(x, pred, color='red', label='fit')
+plt.legend()
+plt.show()
+
+ +

运行结果如下:

+ +

Cox-stuart 趋势检验

+ +

基本原理

+ +

Cox-Stuart 趋势检验过程直接考虑数据的变化趋势, 若数据有上升趋势, 那么排在后面的数据的值要比排在前面的数据的值显著的大, 反之若数据有下降趋势, 那么排在后面的数据的值要比排在前面的数据的值显著的小, 利用前后两个时期不同数据的差值正负来判断数据总的变化趋势.

+ +

算法过程

+ +

假设 $n$ 个数据形成数据列 $X = [x_1, x_2, …, x_n]$, +取 $x_i$ 和 $x_{i+c}$ 组成一对 $(x_i, x_{i+c})$, 这里如果 $n$ 为偶数,则 $c=\frac{n}{2}$ ,如果 $n$ 是奇数,则 $c=\frac{(n+1)}{2}$, 当 $n$ 为偶数时,数据对共有 $n’=c$ 对, 而 $n$ 是奇数时, 共有 $n’=c-1$ 对.

+ +

用每一对的两元素差 $D_i = x_i − x_{i+c}$ 的符号来衡量增减, 令 $S+$ 为正的 $D_i$ 的数目, $S-$ 为负的 $D_i$ 的数目. 显然当正号太多时有下降趋势, 反之有增长趋势. 在没有趋势的零假设下他们因服从二项分布 $B(n’, 0.5)$.

+ +

用 $p(+)$ 表示取到正数的概率, 用 $p(-)$ 表示取到负数的概率, 这样我们就得到符号检验方法来检验序列是否存在趋势性.

+ +

本方法优点是可以不依赖趋势结构, 快速判断出趋势是否存在. 缺点是未考虑数据的时序性, 仅仅只能通过符号检验来判断.

+ +

实验:

+ +
import scipy.stats as stats
+
+
+def cos_stuart(x):
+    n = len(x)
+    xx = x  # 因为需要删除,所以复制一份
+    if n % 2 == 1:
+        del xx[n // 2]
+    c = n // 2
+
+    # 计算正负符号的数量
+    n_pos = n_neg = 0  # n_pos=S+  n_neg=S-
+    for i in range(c):
+        diff = xx[i + c] - x[i]
+        if diff > 0:
+            n_pos += 1
+        elif diff < 0:
+            n_neg += 1
+        else:
+            continue
+
+    num = n_pos + n_neg
+    k = min(n_pos, n_neg)  # 求K值
+    p_value = 2 * stats.binom.cdf(k, num, 0.5)  # 计算p_value
+    print("fall:{}, rise:{}, p-value:{}".format(n_neg, n_pos, p_value))
+
+    # p_value<0.05,零假设不成立
+    if n_pos > n_neg and p_value < 0.05:
+        return "increasing"
+    elif n_neg > n_pos and p_value < 0.05:
+        return "decreasing"
+    else:
+        return "no trend"
+
+x = list(range(1, 20))
+cos_stuart(x)
+
+#>>> 输出 <<<
+#> fall:0, rise:9, p-value:0.00390625
+#> 'increasing'
+
+ +

Mann-Kendall 趋势检验

+ +

基本原理

+ +

使用 MK 算法检验时序数据大致趋势, 趋势分为无明显趋势(稳定), 趋势上升, 趋势下降.

+ +

MK 检验的基础:

+ +
    +
  • 当没有趋势时, 随时间获得的数据是独立同分布的, 数据随着时间不是连续相关的.
  • +
  • 所获得的时间序列上的数据代表了采样时的真实条件, 样本要具有代表性.
  • +
  • MK 检验不要求数据是正态分布, 也不要求变化趋势是线性的.
  • +
  • 如果有缺失值或者值低于一个或多个检测限制, 是可以计算 MK 检测的, 但检测性能会受到不利影响.
  • +
  • 独立性假设要求样本之间的时间足够大, 这样在不同时间收集的测量值之间不存在相关性
  • +
+ +

算法过程(没看懂)

+ +

第一部分, 计算趋势

+ +
    +
  1. 设零假设 $H_0$ 没有单调趋势, $H_a$ 有单调趋势
  2. +
  3. 数据按照采集时间一次取出, 记为 $X = [x_1, x_2, x_3, …, x_n]$
  4. +
  5. +

    确定所有 $C_n^2$ 个 $x_j - x_i$ 的差值函数 $Sign(x_j - x_i)$, $1 \le j \lt i \le n$, 函数定义如下:

    + +\[Sign(x_j - x_i) = \begin{cases} + -1, & x_j - x_i \lt 0 \\ + 0, & x_j - x_i = 0, 或者数据缺失 \\ + 1, & x_j - x_i \gt 0 \\ +\end{cases}\] +
  6. +
  7. +

    求检验统计量

    + +\[S = \sum_{i=1}^{n-1} \sum_{j=i+1}^{n} Sign(x_j - x_i)\] + +

    如果 $S$ 是一个正数, 那么后一部分的观测值相比之前的观测值会趋向于变大, 如果 $S$ 是一个负数, 那么后一部分的观测值相比之前的观测值会趋向于变小.

    +
  8. +
  9. +

    如果 $n$ 比较小(比如小于 8), 依据 Gilbert (1987, page 209, Section 16.4.1)中所描述的程序, 要去概率表(Gilbert 1987, Table A18, page 272)里面查找 $S$, 这个表目前找不到了, 这里给出一个 $S$ 和 $Z$ 的关系式:

    + +\[Z = \frac{S}{\frac{n(n-1)}{2}}\] + +

    如果此概率小于 $\alpha$ (认为没有趋势时的截止概率), 那就拒绝零假设, 认为趋势存在. +如果在概率表中找不到 $n$ (存在结数据(tied data values)会发生此情况), 就用表中远离 $0$ 的下一个值, 比如如果概率表中没有 $S=12$, 那么就用 $S=13$ 来处理也是一样的.

    + +

    +
  10. +
  11. +

    当 $n \ge 8$ 时, 按照 Gilbert (1987, page 211, Section 16.4.2)中的程序, 统计量 $S$ 大致的服从正态分布, 方差通过以下方式计算

    + +

    如果数据中, 每个数字都是唯一的, 则方差

    + +\[Var(S)= \frac{n(n-1)(2n+5)}{18}\] + +

    如果数据中数据存在不唯一, 则公式变为

    + +\[Var(S)= \frac{n(n-1)(2n+5) - \sum_{p=1}^{g} t_p(t_p-1)(2t_p+5)}{18}\] + +

    其中, $p$ 为重复数据数量, $g$ 为唯一数数量(结组数), $t_p$ 为每个重复数重复的次数.

    + +

    当因为有或者未检测到而出现结时, $Var(S)$ 可以通过 Helsel(2005, p.191)中的结修正方法来修正.

    +
  12. +
  13. +

    计算标准化后的检验统计量 Z 如下:

    + +\[Z_{MK} = \begin{cases} + \frac{S-1}{\sqrt{V_{ar}(S)}}, & S>0 \\ + 0, & S=0 \\ + \frac{S+1}{\sqrt{V_{ar}(S)}}, & S<0 \\ +\end{cases}\] +
  14. +
  15. +

    回到 $H_0$ 和 $H_a$ 假设

    + +
    +

    什么是一型错误和二型错误

    + +
      +
    • 一型错误: 原假设是正确的, 推翻了原假设造成的错误
    • +
    • 二型错误: 原假设是错误的, 没有推翻原假设造成的错误
    • +
    +
    + +

    其一型错误率为 $\alpha$, $0 \lt \alpha \lt 0.5$(注意 $\alpha$ 是 MK 检验错误地拒绝了零假设时可容忍的概率, 即 MK 检验拒绝了零假设是错误的, 但这个事情发生概率是 $\alpha$, 我们可以容忍这个错误).

    + +
      +
    1. +

      针对 $H_0$ 没有单调趋势, $H_a$ 有单调增趋势

      + +

      如果 $Z_{MK} \ge Z_{1 - \alpha}$, 就拒绝零假设 $H_0$, 接受替代假设 $H_a$. 其中 $Z_{1 - \alpha}$ 是标准正态分布的 $100(1-\alpha)^{th}$ 百分位(percentile, 不是很懂他说的这些是什么, 需要看一下参考文献). 这些百分位在许多统计书(比如 Gilbert 1987, Table A1, page 254)和统计软件包中都有提供.

      +
    2. +
    3. +

      针对 $H_0$ 没有单调趋势, $H_a$ 有单调减趋势

      + +

      如果 $Z_{MK} \le - Z_{1 - \alpha}$, 就拒绝零假设 $H_0$, 接受替代假设 $H_a$.

      +
    4. +
    5. +

      针对 $H_0$ 没有单调趋势, $H_a$ 有单调递增或递减趋势

      + +

      如果 $\lvert Z_{MK} \rvert \ge Z_{1 - \frac{\alpha}{2}} $, 就拒绝零假设 $H_0$, 接受替代假设 $H_a$.

      +
    6. +
    +
  16. +
  17. +

    最终得到错误率为 $\alpha$, $0 \lt \alpha \lt 0.5$, $\lvert Z_{MK} \rvert \ge Z_{1-\frac{\alpha}{2}}$, $Z_{1-\frac{\alpha}{2}}$ 为 $ppf(1-\frac{\alpha}{2})$(为什么是 $1 - \frac{\alpha}{2}$, 是因为概率为正态分布而且左右两边均存在, 且 $1-\frac{\alpha}{2}$ 为置信度)

    + +

    在双边趋势检验中, 对于给定的置信水平 $\alpha$ (显著性水平), 若 $\lvert Z \rvert \ge Z_{1 - \frac{\alpha}{2}}$, 则原假设 $H_0$ 是不可接受的, 即在置信水平 $\alpha$ (显著性检验水平)上, 时间序列数据存在明显的上升或下降趋势. $Z$ 为正值表示上升趋势, 负值表示减少趋势, $Z$ 的绝对值在大于等于 $1.645, 1.96, 2.576$ 时表示分别通过了置信度 $90\%, 95\%, 99\%$ 的显著性检验.

    + +

    计算过程: 以 $ \alpha = 0.1$ 为例, $Z_{1-\frac{\alpha}{2}} = Z_{0.95}$, 查询标准正态分布表 $Z_{0.95} = 1.645$, 故 $Z \ge 1.645$ 时通过 $90\%$ 的显著性检验, $H_0$ 假设不成立, $Z \gt 0$, 序列存在上升趋势.

    +
  18. +
  19. +

    衡量趋势大小的指标, 用倾斜度 $\beta$ 表示为:

    + +\[\beta = median(\frac{x_j - x_i}{j - i}) \quad \forall \space 1 \lt i \lt j \lt n\] +
  20. +
  21. +

    $P$ 值计算验证(可选)

    + +\[P = 2(1-cdf(\lvert Z_{MK} \rvert))\] +
  22. +
+ +

第二部分, 查找突变点

+ +
    +
  1. +

    设 $S_k$ 表示 $X$ 中的第 $j$ 个样本 $x_j \gt x_i (1 \le i \le j)$ 的累计数, 定义统计量 $S_k$:

    + +\[S_k = \sum_{j=1}^k r_j \quad (r_j = \begin{cases} + 1 & x_j \gt x_i \\ + 0 & x_j \le x_i \\ +\end{cases}, \quad i=1,2,...,j;k=1,2,....n)\] +
  2. +
  3. +

    在时间序列随机独立的假定下, $S_k$ 的均值为:

    + +\[E[S_k] = \frac{k(k-1)}{4}\] + +

    方差为

    + +\[Var[S_k] = \frac{k(k-1)(2k+5)}{72} \quad 1 \le k \le n\] +
  4. +
  5. +

    将 $S_k$ 标准化:

    + +

    其中 $UF_1=0$, 给定显著性水平 $\alpha$, 若 $\lvert UF_k \rvert \gt U_{\alpha}$, 则表明序列存在明显的趋势变化. 所有 $UF_k$ 可组成一条曲线, 将此方法引用到反序列, 将反序列 $x_n, x_{n-1}, …, x_1$ 表示为 $x’_1, x’_2, …, x’_n$. $j$ 表示第 $j$ 个样本 $x_j$ 大于 $x_i (j \le i \le n)$ 的累计数. 当 $j’ = n+1-j$ 时, $j = r’j$, 则反序列的 $UB_k$ 为:

    + +\[UB_k = -UF_k \quad k'=n+1-j \quad j,j'=1,2,...,n...\] + +

    其中 $UB_1=0$, $UB_k$ 不是简单的等于 $UF_k$ 负值, 而是进行了倒置再取负, 此处 $UF_k$ 是根据反序列算出来的.

    + +

    给定显著性水平, 若 $\alpha =0.05$, 那么临界值为 $\pm 1.96$, 绘制 $UF_k$ 和 $UB_k$ 曲线图和 $\pm 1.96$ 俩条直线再一张图上, 若 $UF_k$ 得值大于 $0$, 则表明序列呈现上升趋势, 小于 $0$ 则表明呈现下降趋势, 当它们超过临界直线时, 表明上升或下降趋势显著. 超过临界线的范围确定为出现突变的时间区域. 如果 $UF_k$ 和 $UB_k$ 两条曲线出现交点, 且交点在临界线内, 那么交点对应的时刻便是突变开始的时间.

    +
  6. +
+ +

优点: 功能强大, 不需要样本遵从一定的分布, 部分数据缺失不会对结果造成影响, 不受少数异常值的干扰, 适用性强. 不但可以检验时间序列的变化趋势, 还可以检验时间序列是否发生了突变.

+ +

缺点: 暂未发现, 待后续补充.

+ +

主要参考资料:

+ + + +

更多资料:

+ +]]>
Zhigang Song
Monkey Patching in Go2023-06-29T22:28:04+08:002023-06-29T22:28:04+08:00http://sidgwick.github.io/2023/06/29/monkey-patching-in-go +

本文是 Monkey Patching in Go 的阅读理解.

+ + +

原文 Monkey Patching in Go

+ +

Many people think that monkey patching is something that is restricted to dynamic languages like Ruby and Python. That is not true however, as computers are just dumb machines and we can always make them do what we want! Let’s look at how Go functions work and how we can modify them at runtime. This article will use a lot of Intel assembly syntax, so I’m assuming you can read it already or are using a reference while reading.

+ + + +

If you’re not interested in how it works and you just want to do monkey patching, then you can find the library here.

+ +

Let’s look at what the following code produces when disassembled:

+ +
package main
+
+func axx() int {
+    return 1
+}
+
+func main() {
+    print(axx())
+}
+
+ +
+

Samples should be built with go build -gcflags='-N -l' to disable inlining. For this article I assume your architecture is 64-bits and that you’re using a unix-based operating system like Mac OSX or a Linux variant. +编译参数说明可以参考: Go gcflags/ldflags 的说明

+
+ +

When compiled and looked at through Hopper, the above code will produce this assembly code:

+ +

I will be referring to the addresses of the various instructions displayed on the left side of the screen.

+ +

+ +

Our code starts in procedure main.main, where instructions 0x2010 to 0x2026 set up the stack. You can read more about that here, I will be ignoring that code for the rest of the article.

+ +

Line 0x202a is the call to function main.a at line 0x2000 which simply moves 0x1 onto the stack and returns. Lines 0x202f to 0x2037 then pass that value on to runtime.printint.

+ +

Simple enough! Now let’s take a look at how function values are implemented in Go.

+ +

How function values work in Go

+ +

Consider the following code:

+ +
package main
+
+import (
+	"fmt"
+	"unsafe"
+)
+
+func a() int { return 1 }
+
+func main() {
+	f := a
+	fmt.Printf("0x%x\n", *(*uintptr)(unsafe.Pointer(&f)))
+}
+
+ +
+

Go 世界中的函数是一等公民

+
+ +

What I’m doing on line 11 is assigning a to f, which means that doing f() will now call a. Then I use the unsafe Go package to directly read out the value stored in f. If you come from a C background you might expect f to simply be a function pointer to a and thus this code to print out 0x2000 (the location of main.a as we saw above). When I run this on my machine I get 0x102c38, which is an address not even close to our code! When disassembled, this is what happens on line 11 above:

+ +

+ +

This references something called main.a.f, and when we look at that location, we see this:

+ +

+ +

Aha! main.a.f is at 0x102c38 and contains 0x2000, which is the location of main.a. It seems f isn’t a pointer to a function, but a pointer to a pointer to a function. Let’s modify the code to compensate for that.

+ +
package main
+
+import (
+	"fmt"
+	"unsafe"
+)
+
+func a() int { return 1 }
+
+func main() {
+	f := a
+	fmt.Printf("0x%x\n", **(**uintptr)(unsafe.Pointer(&f)))
+}
+
+ +
+

胖指针!!!

+
+ +

This will now print 0x2000, as expected. We can find a clue as to why this is implemented as it is here. Go function values can contain extra information, which is how closures and bound instance methods are implemented.

+ +

Let’s look at how calling a function value works. I’ll change the code to call f after assigning it.

+ +
package main
+
+func a() int { return 1 }
+
+func main() {
+	f := a
+	f()
+}
+
+ +

When we disassemble this we get the following:

+ +

+ +

main.a.f gets loaded into rdx, then whatever rdx points at gets loaded into rbx, which then gets called. The address of the function value always gets loaded into rdx, which the code being called can use to load any extra information it might need. This extra information is a pointer to the instance for a bound instance method and the closure for an anonymous function. I advise you to take out a disassembler and dive deeper if you want to know more!

+ +

Let’s use our newly gained knowledge to implement monkeypatching in Go.

+ +

Replacing a function at runtime

+ +

What we want to achieve is to have the following code print out 2:

+ +
package main
+
+func a() int { return 1 }
+func b() int { return 2 }
+
+func main() {
+	replace(a, b)
+	print(a())
+}
+
+ +

Now how do we implement replace? We need to modify function a to jump to b’s code instead of executing its own body. Essentialy, we need to replace it with this, which loads the function value of b into rdx and then jumps to the location pointed to by rdx.

+ +
mov rdx, main.b.f ; 48 C7 C2 ?? ?? ?? ??
+jmp [rdx] ; FF 22
+
+ +

I’ve put the corresponding machine code that those lines generate when assembled next to it (you can easily play around with assembly using an online assembler like this). Writing a function that will generate this code is now straightforward, and looks like this:

+ +
+

关于这段汇编的解释, 实际上这是作者使用了两个汇编指令(就是上面的 movjmp).

+ +

假如说我们想把 a() 调用替换为 b(), 而且 b 函数在生成的可执行文件中的内存位置为 0x01020304, 那么, 需要做的就是生成下面这样的机器码(后面是对应的汇编指令):

+ +
9:	48 c7 c2 04 03 02 01 	mov    $0x01020304,%rdx
+10:	ff 22                	jmpq   *(%rdx)
+
+ +

只需要把这段机器码替换到原始函数的机器码开始位置, 就能实现调用 a 的时候跳转到 b. 从原理上说, 我们还是调用的 a 函数, 不过在 a 函数的开头现在有个跳转指令, 直接跳转到了 b 函数的位置执行, 执行完成之后由 b 函数里面的 ret 指令直接返回到 a 的调用者的下一条指令继续执行. 函数参数和返回值是依赖栈帧传递的, 这里替换之后不会影响栈, 因此也不会影响参数和返回值.

+ +

assembleJump 函数就是用来生成这种机器码的.

+
+ +
import "unsafe"
+
+func assembleJump(f func() int) []byte {
+	funcVal := *(*uintptr)(unsafe.Pointer(&f))
+	return []byte{
+		0x48, 0xC7, 0xC2,
+		byte(funcval >> 0),
+		byte(funcval >> 8),
+		byte(funcval >> 16),
+		byte(funcval >> 24), // MOV rdx, funcVal
+		0xFF, 0x22,          // JMP [rdx]
+	}
+}
+
+ +

We now have everything we need to replace a’s function body with a jump to b! The following code attempts to copy the machine code directly to the location of the function body.

+ +
package main
+
+import (
+	"syscall"
+	"unsafe"
+)
+
+func a() int { return 1 }
+func b() int { return 2 }
+
+// 这里获取运行时代码段中 `b` 代表的函数指针的内存区域
+// 通过覆写这块区域, 就可以实现函数指针指向偷天换日的效果
+func rawMemoryAccess(b uintptr) []byte {
+	return (*(*[0xFF]byte)(unsafe.Pointer(b)))[:]
+}
+
+func assembleJump(f func() int) []byte {
+	funcVal := *(*uintptr)(unsafe.Pointer(&f))
+	return []byte{
+		0x48, 0xC7, 0xC2,
+		byte(funcVal >> 0),
+		byte(funcVal >> 8),
+		byte(funcVal >> 16),
+		byte(funcVal >> 24), // MOV rdx, funcVal
+		0xFF, 0x22, // JMP [rdx]
+	}
+}
+
+func replace(orig, replacement func() int) {
+	bytes := assembleJump(replacement)
+	functionLocation := **(**uintptr)(unsafe.Pointer(&orig))
+	window := rawMemoryAccess(functionLocation)
+	copy(window, bytes)
+}
+
+func main() {
+	replace(a, b)
+	print(a())
+}
+
+ +

Running this code does not work however, and will result in a segmentation fault. This is because the loaded binary is not writable by default. We can use the mprotect syscall to disable this protection, and this final version of the code does exactly that, resulting in function a being replaced by function b, and 2 being printed.

+ +
package main
+
+import (
+	"syscall"
+	"unsafe"
+)
+
+func a() int { return 1 }
+func b() int { return 2 }
+
+func getPage(p uintptr) []byte {
+	return (*(*[0xFFFFFF]byte)(unsafe.Pointer(p & ^uintptr(syscall.Getpagesize()-1))))[:syscall.Getpagesize()] // 拷贝一页 16Kb
+}
+
+func rawMemoryAccess(b uintptr) []byte {
+	return (*(*[0xFF]byte)(unsafe.Pointer(b)))[:] // 拷贝 255 个内存单元出去, 实际上只需要操作 9 个, 这里拷贝的数量大于 9 个就行
+}
+
+func assembleJump(f func() int) []byte {
+	funcVal := *(*uintptr)(unsafe.Pointer(&f))
+	return []byte{
+		0x48, 0xC7, 0xC2,
+		byte(funcVal >> 0),
+		byte(funcVal >> 8),
+		byte(funcVal >> 16),
+		byte(funcVal >> 24), // MOV rdx, funcVal
+		0xFF, 0x22, // JMP rdx
+	}
+}
+
+func replace(orig, replacement func() int) {
+	bytes := assembleJump(replacement)
+	functionLocation := **(**uintptr)(unsafe.Pointer(&orig))
+	window := rawMemoryAccess(functionLocation)
+	page := getPage(functionLocation)
+	syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC)
+	copy(window, bytes)
+}
+
+func main() {
+	replace(a, b)
+	print(a())
+}
+
+ +

Wrapping it up in a nice library

+ +

I took the above code and put it in an easy to use library. It supports 32 bit, reversing patches, and patching instance methods. I wrote a couple of examples and put those in the README.

+ +

Conclusion

+ +

Where there’s a will there’s a way! It’s possible for a program to modify itself at runtime, which allows us to implement cool tricks like monkey patching.

+ +

I hope you got something useful out of this blogpost, I know I had fun making it!

+ +

个人补充

+ +
+

这块我在研究上面的时候的实验尝试, 无论理解/不理解 Go 的汇编相关的东西, 对上文的阅读是没有影响的, 因此这部分 不用看

+
+ +

本文中用到的编译/汇编, 使用下面的指令完成:

+ +

编译为目标文件

+ +
> go tool compile -N -l main.go
+
+ +

目标文件反汇编

+ +
> go tool objdump -gnu main.o
+
+ +
TEXT %22%22.axx(SB) gofile../root/xxx/main.go
+  main.go:3		0x428			48c744240800000000	MOVQ $0x0, 0x8(SP)                   // movq $0x0,0x8(%rsp)
+  main.go:4		0x431			48c744240801000000	MOVQ $0x1, 0x8(SP)                   // movq $0x1,0x8(%rsp)
+  main.go:4		0x43a			c3			        RET                                  // retq
+
+TEXT %22%22.main(SB) gofile../root/xxx/main.go
+  main.go:7		0x43b			64488b0c2500000000	MOVQ FS:0, CX                    // mov %fs:,%rcx		[5:9]R_TLS_LE
+  main.go:7		0x444			483b6110		CMPQ 0x10(CX), SP                    // cmp 0x10(%rcx),%rsp
+  main.go:7		0x448			7645			JBE 0x48f                            // jbe 0x48f
+  main.go:7		0x44a			4883ec18		SUBQ $0x18, SP                       // sub $0x18,%rsp
+  main.go:7		0x44e			48896c2410		MOVQ BP, 0x10(SP)                    // mov %rbp,0x10(%rsp)
+  main.go:7		0x453			488d6c2410		LEAQ 0x10(SP), BP                    // lea 0x10(%rsp),%rbp
+  main.go:8		0x458			0f1f00			NOPL 0(AX)                           // nopl (%rax)
+  main.go:8		0x45b			e800000000		CALL 0x460                           // callq 0x460		[1:5]R_CALL:"".axx
+  main.go:8		0x460			488b0424		MOVQ 0(SP), AX                       // mov (%rsp),%rax
+  main.go:8		0x464			4889442408		MOVQ AX, 0x8(SP)                     // mov %rax,0x8(%rsp)
+  main.go:8		0x469			e800000000		CALL 0x46e                           // callq 0x46e		[1:5]R_CALL:runtime.printlock
+  main.go:8		0x46e			488b442408		MOVQ 0x8(SP), AX                     // mov 0x8(%rsp),%rax
+  main.go:8		0x473			48890424		MOVQ AX, 0(SP)                       // mov %rax,(%rsp)
+  main.go:8		0x477			0f1f4000		NOPL 0(AX)                           // nopl (%rax)
+  main.go:8		0x47b			e800000000		CALL 0x480                           // callq 0x480		[1:5]R_CALL:runtime.printint
+  main.go:8		0x480			e800000000		CALL 0x485                           // callq 0x485		[1:5]R_CALL:runtime.printunlock
+  main.go:9		0x485			488b6c2410		MOVQ 0x10(SP), BP                    // mov 0x10(%rsp),%rbp
+  main.go:9		0x48a			4883c418		ADDQ $0x18, SP                       // add $0x18,%rsp
+  main.go:9		0x48e			c3			    RET                                  // retq
+  main.go:7		0x48f			e800000000		CALL 0x494                           // callq 0x494		[1:5]R_CALL:runtime.morestack_noctxt
+  main.go:7		0x494			eba5			JMP %22%22.main(SB)                  // jmp 0x43b
+
+ +

对上面汇编的一点解释:

+ +
+

预备知识:

+ +
    +
  1. 线程本地存储 TLS
  2. +
  3. GO 语言的运行时初始化过程解析
  4. +
+
+ +
    +
  1. 0x43b ~ 0x448 是 Go 进入函数执行之前的初始化工作, 其实就是设置 TLS 以及运行时栈大小检查/分配
  2. +
  3. 0x44a ~ 0x453 是为 main 函数设置运行时栈, 栈空间大小设置为 0x10, 栈低指针被设置为 0x10(%rsp), 也就是 rsp 再加上 10 的位置, 栈顶指针被设置为 0x18(%rsp) 位置.
  4. +
  5. 0x458 nopl 是一个无意义的指令, 纯粹是为了占位置. 如果函数有参数的话, 这里会是设置 axx 参数相关代码, axx 收到的参数实际上.
  6. +
  7. 0x45b 发起对函数 axx 的调用
  8. +
+ +

栈设置的具体解释(注意栈是往下生长的):

+ +
; 开辟栈空间, 操作前的栈顶记为 SP0, 栈顶指针现在是 SP0 - 0x18, 记为 SP1
+; 此时 SP0-SP1 之间有 0x18/8 = 3  64 位存储单元: (高地址, sp0) [X, X, X] (低地址, sp1)
+sub $0x18,%rsp
+
+; 把原来的栈底记作 BP0, 这一步将它保存在 `0x10(%rsp)`
+; 操作完了之后栈内容变为 (高地址, sp0) [BP0, X, X] (低地址, sp1)
+mov %rbp,0x10(%rsp)
+
+; 设置新的栈低, 操作完之后, 栈内容不变, 只是 BP 寄存器发生了变化
+lea 0x10(%rsp),%rbp
+
+; 剩下的那两个栈存储区域,  main 函数的其他地方用到了, 可以看上面完整的汇编
+
]]>
Zhigang Song
python 的 metaclass 到底是什么2023-04-14T10:28:04+08:002023-04-14T10:28:04+08:00http://sidgwick.github.io/2023/04/14/metaclass +

文章地址: https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python?answertab=votes#tab-top

+ + +

Classes as objects

+ +

Before understanding metaclasses, it helps to understand Python classes more deeply. Python has a very peculiar idea of what classes are, which it borrows from the Smalltalk language.

+ +

In most languages, classes are just pieces of code that describe how to produce an object. That is somewhat true in Python too:

+ +
>>> class ObjectCreator(object):
+...       pass
+
+>>> my_object = ObjectCreator()
+>>> print(my_object)
+<__main__.ObjectCreator object at 0x8974f2c>
+
+ + + +

But classes are more than that in Python. Classes are objects too.

+ +

Yes, objects.

+ +

When a Python script runs, every line of code is executed from top to bottom. When the Python interpreter encounters the class keyword, Python creates an object out of the “description” of the class that follows. Thus, the following instruction

+ +
>>> class ObjectCreator(object):
+...       pass
+
+ +

…creates an object with the name ObjectCreator!

+ +

This object (the class) is itself capable of creating objects (called instances).

+ +

But still, it’s an object. Therefore, like all objects:

+ +

you can assign it to a variable1

+ +
JustAnotherVariable = ObjectCreator
+
+ +

you can attach attributes to it

+ +
ObjectCreator.class_attribute = 'foo'
+
+ +

you can pass it as a function parameter

+ +
print(ObjectCreator)
+
+ +

Note that merely assigning it to another variable doesn’t change the class’s name, i.e.,

+ +
>>> print(JustAnotherVariable)
+<class '__main__.ObjectCreator'>
+
+>>> print(JustAnotherVariable())
+<__main__.ObjectCreator object at 0x8997b4c>
+
+ +

Creating classes dynamically

+ +

Since classes are objects, you can create them on the fly, like any object.

+ +

First, you can create a class in a function using class:

+ +
>>> def choose_class(name):
+...     if name == 'foo':
+...         class Foo(object):
+...             pass
+...         return Foo # return the class, not an instance
+...     else:
+...         class Bar(object):
+...             pass
+...         return Bar
+...
+>>> MyClass = choose_class('foo')
+>>> print(MyClass) # the function returns a class, not an instance
+<class '__main__.Foo'>
+>>> print(MyClass()) # you can create an object from this class
+<__main__.Foo object at 0x89c6d4c>
+
+ +

But it’s not so dynamic, since you still have to write the whole class yourself.

+ +

Since classes are objects, they must be generated by something.

+ +

When you use the class keyword, Python creates this object automatically. But as with most things in Python, it gives you a way to do it manually.

+ +

Remember the function type? The good old function that lets you know what type an object is:

+ +
>>> print(type(1))
+<type 'int'>
+>>> print(type("1"))
+<type 'str'>
+>>> print(type(ObjectCreator))
+<type 'type'>
+>>> print(type(ObjectCreator()))
+<class '__main__.ObjectCreator'>
+
+ +

Well, type has also a completely different ability: it can create classes on the fly. type can take the description of a class as parameters, and return a class.

+ +

(I know, it’s silly that the same function can have two completely different uses according to the parameters you pass to it. It’s an issue due to backward compatibility in Python)

+ +

type works this way:

+ +
type(name, bases, attrs)
+
+ +

Where:

+ +
    +
  • name: name of the class
  • +
  • bases: tuple of the parent class (for inheritance, can be empty)
  • +
  • attrs: dictionary containing attributes names and values
  • +
+ +

e.g.:

+ +
>>> class MyShinyClass(object):
+...       pass
+
+ +

can be created manually this way:

+ +
>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
+>>> print(MyShinyClass)
+<class '__main__.MyShinyClass'>
+>>> print(MyShinyClass()) # create an instance with the class
+<__main__.MyShinyClass object at 0x8997cec>
+
+ +

You’ll notice that we use MyShinyClass as the name of the class and as the variable to hold the class reference. They can be different, but there is no reason to complicate things.

+ +

type accepts a dictionary to define the attributes of the class. So:

+ +
>>> class Foo(object):
+...       bar = True
+
+ +

Can be translated to:

+ +
>>> Foo = type('Foo', (), {'bar':True})
+
+ +

And used as a normal class:

+ +
>>> print(Foo)
+<class '__main__.Foo'>
+>>> print(Foo.bar)
+True
+>>> f = Foo()
+>>> print(f)
+<__main__.Foo object at 0x8a9b84c>
+>>> print(f.bar)
+True
+
+ +

And of course, you can inherit from it, so:

+ +
>>>   class FooChild(Foo):
+...         pass
+
+ +

would be:

+ +
>>> FooChild = type('FooChild', (Foo,), {})
+>>> print(FooChild)
+<class '__main__.FooChild'>
+>>> print(FooChild.bar) # bar is inherited from Foo
+True
+
+ +

Eventually, you’ll want to add methods to your class. Just define a function with the proper signature and assign it as an attribute.

+ +
>>> def echo_bar(self):
+...       print(self.bar)
+...
+>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
+>>> hasattr(Foo, 'echo_bar')
+False
+>>> hasattr(FooChild, 'echo_bar')
+True
+>>> my_foo = FooChild()
+>>> my_foo.echo_bar()
+True
+
+ +

And you can add even more methods after you dynamically create the class, just like adding methods to a normally created class object.

+ +
>>> def echo_bar_more(self):
+...       print('yet another method')
+...
+>>> FooChild.echo_bar_more = echo_bar_more
+>>> hasattr(FooChild, 'echo_bar_more')
+True
+
+ +

You see where we are going: in Python, classes are objects, and you can create a class on the fly, dynamically.

+ +

This is what Python does when you use the keyword class, and it does so by using a metaclass.

+ +

What are metaclasses (finally)

+ +

Metaclasses are the ‘stuff’ that creates classes.

+ +

You define classes in order to create objects, right?

+ +

But we learned that Python classes are objects.

+ +

Well, metaclasses are what create these objects. They are the classes’ classes, you can picture them this way:

+ +
MyClass = MetaClass()
+my_object = MyClass()
+
+ +

You’ve seen that type lets you do something like this:

+ +
MyClass = type('MyClass', (), {})
+
+ +

It’s because the function type is in fact a metaclass. type is the metaclass Python uses to create all classes behind the scenes.

+ +

Now you wonder “why the heck is it written in lowercase, and not Type?”

+ +

Well, I guess it’s a matter of consistency with str, the class that creates strings objects, and int the class that creates integer objects. type is just the class that creates class objects.

+ +

You see that by checking the __class__ attribute.

+ +

Everything, and I mean everything, is an object in Python. That includes integers, strings, functions and classes. All of them are objects. And all of them have been created from a class:

+ +
>>> age = 35
+>>> age.__class__
+<type 'int'>
+>>> name = 'bob'
+>>> name.__class__
+<type 'str'>
+>>> def foo(): pass
+>>> foo.__class__
+<type 'function'>
+>>> class Bar(object): pass
+>>> b = Bar()
+>>> b.__class__
+<class '__main__.Bar'>
+
+ +

Now, what is the __class__ of any __class__ ?

+ +
>>> age.__class__.__class__
+<type 'type'>
+>>> name.__class__.__class__
+<type 'type'>
+>>> foo.__class__.__class__
+<type 'type'>
+>>> b.__class__.__class__
+<type 'type'>
+
+ +

So, a metaclass is just the stuff that creates class objects.

+ +

You can call it a ‘class factory’ if you wish.

+ +

type is the built-in metaclass Python uses, but of course, you can create your own metaclass.

+ +

The __metaclass__ attribute

+ +

In Python 2, you can add a __metaclass__ attribute when you write a class (see next section for the Python 3 syntax):

+ +
class Foo(object):
+    __metaclass__ = something...
+    [...]
+
+ +

If you do so, Python will use the metaclass to create the class Foo.

+ +

Careful, it’s tricky.

+ +

You write class Foo(object) first, but the class object Foo is not created in memory yet.

+ +

Python will look for __metaclass__ in the class definition. If it finds it, it will use it to create the object class Foo. If it doesn’t, it will use type to create the class.

+ +

Read that several times.

+ +

When you do:

+ +
class Foo(Bar):
+    pass
+
+ +

Python does the following:

+ +

Is there a __metaclass__ attribute in Foo?

+ +

If yes, create in-memory a class object (I said a class object, stay with me here), with the name Foo by using what is in __metaclass__.

+ +

If Python can’t find __metaclass__, it will look for a __metaclass__ at the MODULE level, and try to do the same (but only for classes that don’t inherit anything, basically old-style classes).

+ +

Then if it can’t find any __metaclass__ at all, it will use the Bar’s (the first parent) own metaclass (which might be the default type) to create the class object.

+ +
+

TODO: 下面这段什么意思? +Be careful here that the __metaclass__ attribute will not be inherited, the metaclass of the parent (Bar.__class__) will be. If Bar used a __metaclass__ attribute that created Bar with type() (and not type.__new__()), the subclasses will not inherit that behavior.

+
+ +

Now the big question is, what can you put in __metaclass__?

+ +

The answer is something that can create a class.

+ +

And what can create a class? type, or anything that subclasses or uses it.

+ +

Metaclasses in Python 3

+ +

The syntax to set the metaclass has been changed in Python 3:

+ +
class Foo(object, metaclass=something):
+    ...
+
+ +

i.e. the __metaclass__ attribute is no longer used, in favor of a keyword argument in the list of base classes.

+ +

The behavior of metaclasses however stays largely the same.

+ +

One thing added to metaclasses in Python 3 is that you can also pass attributes as keyword-arguments into a metaclass, like so:

+ +
class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
+    ...
+
+ +

Read the section below for how Python handles this.

+ +

Custom metaclasses

+ +

The main purpose of a metaclass is to change the class automatically, when it’s created.

+ +

You usually do this for APIs, where you want to create classes matching the current context.

+ +

Imagine a stupid example, where you decide that all classes in your module should have their attributes written in uppercase. There are several ways to do this, but one way is to set __metaclass__ at the module level.

+ +

This way, all classes of this module will be created using this metaclass, and we just have to tell the metaclass to turn all attributes to uppercase.

+ +

Luckily, __metaclass__ can actually be any callable, it doesn’t need to be a formal class (I know, something with ‘class’ in its name doesn’t need to be a class, go figure… but it’s helpful).

+ +

So we will start with a simple example, by using a function.

+ +
# the metaclass will automatically get passed the same argument
+# that you usually pass to `type`
+def upper_attr(future_class_name, future_class_parents, future_class_attrs):
+    """
+      Return a class object, with the list of its attribute turned
+      into uppercase.
+    """
+    # pick up any attribute that doesn't start with '__' and uppercase it
+    uppercase_attrs = {
+        attr if attr.startswith("__") else attr.upper(): v
+        for attr, v in future_class_attrs.items()
+    }
+
+    # let `type` do the class creation
+    return type(future_class_name, future_class_parents, uppercase_attrs)
+
+__metaclass__ = upper_attr # this will affect all classes in the module
+
+class Foo(): # global __metaclass__ won't work with "object" though
+    # but we can define __metaclass__ here instead to affect only this class
+    # and this will work with "object" children
+    bar = 'bip'
+
+ +

Let’s check:

+ +
>>> hasattr(Foo, 'bar')
+False
+>>> hasattr(Foo, 'BAR')
+True
+>>> Foo.BAR
+'bip'
+
+ +

Now, let’s do exactly the same, but using a real class for a metaclass:

+ +
# remember that `type` is actually a class like `str` and `int`
+# so you can inherit from it
+class UpperAttrMetaclass(type):
+    # __new__ is the method called before __init__
+    # it's the method that creates the object and returns it
+    # while __init__ just initializes the object passed as parameter
+    # you rarely use __new__, except when you want to control how the object
+    # is created.
+    # here the created object is the class, and we want to customize it
+    # so we override __new__
+    # you can do some stuff in __init__ too if you wish
+    # some advanced use involves overriding __call__ as well, but we won't
+    # see this
+    def __new__(upperattr_metaclass, future_class_name,
+                future_class_parents, future_class_attrs):
+        uppercase_attrs = {
+            attr if attr.startswith("__") else attr.upper(): v
+            for attr, v in future_class_attrs.items()
+        }
+        return type(future_class_name, future_class_parents, uppercase_attrs)
+
+ +

Let’s rewrite the above, but with shorter and more realistic variable names now that we know what they mean:

+ +
class UpperAttrMetaclass(type):
+    def __new__(cls, clsname, bases, attrs):
+        uppercase_attrs = {
+            attr if attr.startswith("__") else attr.upper(): v
+            for attr, v in attrs.items()
+        }
+        return type(clsname, bases, uppercase_attrs)
+
+ +

You may have noticed the extra argument cls. There is nothing special about it: __new__ always receives the class it’s defined in, as the first parameter. Just like you have self for ordinary methods which receive the instance as the first parameter, or the defining class for class methods.

+ +

But this is not proper OOP. We are calling type directly and we aren’t overriding or calling the parent’s __new__. Let’s do that instead:

+ +
class UpperAttrMetaclass(type):
+    def __new__(cls, clsname, bases, attrs):
+        uppercase_attrs = {
+            attr if attr.startswith("__") else attr.upper(): v
+            for attr, v in attrs.items()
+        }
+        return type.__new__(cls, clsname, bases, uppercase_attrs)
+
+ +

We can make it even cleaner by using super, which will ease inheritance (because yes, you can have metaclasses, inheriting from metaclasses, inheriting from type):

+ +
class UpperAttrMetaclass(type):
+    def __new__(cls, clsname, bases, attrs):
+        uppercase_attrs = {
+            attr if attr.startswith("__") else attr.upper(): v
+            for attr, v in attrs.items()
+        }
+
+        # Python 2 requires passing arguments to super:
+        return super(UpperAttrMetaclass, cls).__new__(
+            cls, clsname, bases, uppercase_attrs)
+
+        # Python 3 can use no-arg super() which infers them:
+        return super().__new__(cls, clsname, bases, uppercase_attrs)
+
+ +

Oh, and in Python 3 if you do this call with keyword arguments, like this:

+ +
class Foo(object, metaclass=MyMetaclass, kwarg1=value1):
+    ...
+
+ +

It translates to this in the metaclass to use it:

+ +
class MyMetaclass(type):
+    def __new__(cls, clsname, bases, dct, kwargs1=default):
+        ...
+
+ +

That’s it. There is really nothing more about metaclasses.

+ +

The reason behind the complexity of the code using metaclasses is not because of metaclasses, it’s because you usually use metaclasses to do twisted stuff relying on introspection, manipulating inheritance, vars such as __dict__, etc.

+ +

Indeed, metaclasses are especially useful to do black magic, and therefore complicated stuff. But by themselves, they are simple:

+ +
    +
  • intercept a class creation
  • +
  • modify the class
  • +
  • return the modified class
  • +
+ +

Why would you use metaclasses classes instead of functions?

+ +

Since __metaclass__ can accept any callable, why would you use a class since it’s obviously more complicated?

+ +

There are several reasons to do so:

+ +
    +
  • The intention is clear. When you read UpperAttrMetaclass(type), you know what’s going to follow
  • +
  • You can use OOP. Metaclass can inherit from metaclass, override parent methods. Metaclasses can even use metaclasses.
  • +
  • Subclasses of a class will be instances of its metaclass if you specified a metaclass-class, but not with a metaclass-function.
  • +
  • You can structure your code better. You never use metaclasses for something as trivial as the above example. It’s usually for something complicated. Having the ability to make several methods and group them in one class is very useful to make the code easier to read.
  • +
  • You can hook on __new__, __init__ and __call__. Which will allow you to do different stuff, Even if usually you can do it all in __new__, some people are just more comfortable using __init__.
  • +
  • These are called metaclasses, damn it! It must mean something!
  • +
+ +

Why would you use metaclasses?

+ +

Now the big question. Why would you use some obscure error-prone feature?

+ +

Well, usually you don’t:

+ +
+

Metaclasses are deeper magic that 99% of users should never worry about it. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why).

+ +

Python Guru Tim Peters

+
+ +

The main use case for a metaclass is creating an API. A typical example of this is the Django ORM. It allows you to define something like this:

+ +
class Person(models.Model):
+    name = models.CharField(max_length=30)
+    age = models.IntegerField()
+
+ +

But if you do this:

+ +
person = Person(name='bob', age='35')
+print(person.age)
+
+ +

It won’t return an IntegerField object. It will return an int, and can even take it directly from the database.

+ +

This is possible because models.Model defines __metaclass__ and it uses some magic that will turn the Person you just defined with simple statements into a complex hook to a database field.

+ +

Django makes something complex look simple by exposing a simple API and using metaclasses, recreating code from this API to do the real job behind the scenes.

+ +

The last word

+ +

First, you know that classes are objects that can create instances.

+ +

Well, in fact, classes are themselves instances. Of metaclasses.

+ +
>>> class Foo(object): pass
+>>> id(Foo)
+142630324
+
+ +

Everything is an object in Python, and they are all either instance of classes or instances of metaclasses.

+ +

Except for type.

+ +

type is actually its own metaclass. This is not something you could reproduce in pure Python, and is done by cheating a little bit at the implementation level.

+ +

Secondly, metaclasses are complicated. You may not want to use them for very simple class alterations. You can change classes by using two different techniques:

+ + + +

99% of the time you need class alteration, you are better off using these.

+ +

But 98% of the time, you don’t need class alteration at all.

+
+
    +
  1. + +

    +
  2. +
+
]]>
Zhigang Song
python 元编程2023-04-14T10:28:04+08:002023-04-14T10:28:04+08:00http://sidgwick.github.io/2023/04/14/meta-programming本文讲述了 python 中 class 的一些细节. 包含:

+ +
    +
  1. class 定义和 class 对象
  2. +
  3. class 实例对象
  4. +
+ +

__new__ 函数和 __call__ 函数

+ +

先从简单的开始, 比如以下代码:

+ + + +
class hello():
+    print('hello 类')
+
+    a = 'aaaa'
+
+    def __new__(cls, *args, **kwargs):
+        print('hello 运行__new__函数')
+        print("cls", cls)
+        print("args", args)
+        print("kwargs", kwargs)
+        print("=" * 20)
+        res = object.__new__(cls)
+        return res
+
+    def __call__(self, *args, **kwargs):
+        print('hello 运行__call__函数')
+        print("self", self)
+        print("args", args)
+        print("kwargs", kwargs)
+        print("=" * 20)
+
+
+print('------ execute -----')
+print(type(hello))
+print(hello.a)
+print('A', '-' * 20)
+obj = hello('HI')
+print('B', '-' * 20)
+print(obj)
+print('C', '-' * 20)
+obj()
+print('D', '-' * 20)
+
+ +

输出为:

+ +
hello 类
+------ execute -----
+<class 'type'>
+aaaa
+A --------------------
+hello 运行__new__函数
+cls <class '__main__.hello'>
+args ('HI',)
+kwargs {}
+====================
+B --------------------
+<__main__.hello object at 0x10ee13fd0>
+C --------------------
+hello 运行__call__函数
+self <__main__.hello object at 0x10ee13fd0>
+args ()
+kwargs {}
+====================
+D --------------------
+
+ +

可以看到在 Python 解释器遇到 class 定义的时候, 就会执行 class body 中的语句. 当实例化一个类的时候, __new__ 方法被调用, 如果尝试把实例对象作为函数调用, 则会触发 __call__ 函数的调用.

+ +

接下来给 hello 指定一个 metaclass.

+ +
class mclass(type):
+    def __new__(cls, clsname, bases, dct):
+        print('mclass - 运行__new__函数')
+        print("cls", cls)
+        print("clsname", clsname)
+        print("bases", bases)
+        print("dct", dct)
+        print("=" * 20)
+        res = super().__new__(cls, clsname, bases, dct)
+        return res
+
+    def __call__(self, *args, **kwargs):
+        print('mclass 对象实例运行__call__函数')
+        print("args", args)
+        print("kwargs", kwargs)
+        print("=" * 20)
+        return self
+
+class world(metaclass=mclass):
+    print("world class")
+
+class hello(metaclass=mclass):
+    ...
+
+ +

这次再执行脚本, 输出如下:

+ +
hello 类
+mclass - 运行__new__函数
+cls <class '__main__.mclass'>
+clsname hello
+bases ()
+dct {'__module__': '__main__', '__qualname__': 'hello', 'a': 'aaaa', '__new__': <function hello.__new__ at 0x10856e3b0>, '__call__': <function hello.__call__ at 0x108622cb0>}
+====================
+world class
+mclass - 运行__new__函数
+cls <class '__main__.mclass'>
+clsname world
+bases ()
+dct {'__module__': '__main__', '__qualname__': 'world'}
+====================
+------ execute -----
+<class '__main__.mclass'>
+aaaa
+A --------------------
+mclass 对象实例运行__call__函数
+args ('HI',)
+kwargs {}
+====================
+B --------------------
+<class '__main__.hello'>
+C --------------------
+mclass 对象实例运行__call__函数
+args ()
+kwargs {}
+====================
+D --------------------
+
+ +

可以看到几点不同:

+ +
    +
  1. mclass 的 __new__ 在解释器遇到 world 或者 hello 类的定义的时候就触发了, 这里应该理解为, 解释器需要使用 metaclass 实例来生成类型对象, 因此就需要在遇到 metaclass=mclass 时候将 mclass 实例化, 所以 mclass 的 __new__ 函数被触发了 2 次
  2. +
  3. hello 类的类型现在变成了 mclass, 这主要是因为指定了元类之后, 类对象就会使用这个元类生成, 因此类型就不再是 type 了(type 本身也是一种元类)
  4. +
  5. hello 类实例化的时候, 触发了元类的 __call__ 函数, 等到再次尝试以函数方式调用实例时, 又触发了元类 __call__ 调用.
  6. +
+ +
+

TODO: 第三点是为啥?

+
]]>
Zhigang Song
sed and awk 101 hacks 笔记2023-03-24T10:28:04+08:002023-03-24T10:28:04+08:00http://sidgwick.github.io/2023/03/24/sed-and-awk-101-hacks +

图书在线阅读地址: sed and awk 101 hacks

+ + +

本文记录 *sed and awk 101 hacks* 一书中学到的知识点备忘.

+ +

sed 部分

+ +
+

本文中的 有些知识并不实用, 如果懵, 不用全都学会, 关键是有机会的时候多用一用, 熟能生巧, 慢慢的就都记住了

+ +

!!! 文中的操作不一定是最简洁的操作, 有些为了演示功能生造出来的使用场景.

+
+ +

本文中使用到的示例文件是系统中的密码文件 passwd:

+ +
root:x:0:0:root:/root:/bin/bash
+bin:x:1:1:bin:/bin:/sbin/nologin
+daemon:x:2:23:daemon:/sbin:/sbin/nologin
+desktop:x:80:80:desktop:/var/lib/menu/kde:/sbin/nologin
+mengqc:x:500:500:mengqc:/home/mengqc:/bin/bash
+
+ + + +

sed 的基本认识

+ +

sed 是一种没有交互的流式编辑器(stream editor), 一般在写脚本的时候用的比较多, 作为文本处理三剑客之一, 如果用的熟练, 在平常工作的时候做一些批量替换批量生成之类的处理也很方便.

+ +

sed 工作的时候, 正常情况下每次读取一行文件内容放到一块叫做 模式空间(pattern space) 的内存区域, 然后 sed 根据给它的指令, 对空间中的文本做编辑/替换等处理操作. 处理过程中并不是它读取到的每一行数据都需要处理, 这时候可以通过指定 地址 约束要处理的文本范围.

+ +

+ +

除了模式空间之外, 还有一块叫做 保持空间(hold space) 的区域, 可以用来暂存数据, sed 提供了专用的命令来操作这两个空间的数据, 通过合理的配合使用, 威力惊人.

+ +

指令详解和使用示例

+ +

先从一个简单的例子开始:

+ +
> cat passwd | sed '3,$s/bash/zsh/'
+root:x:0:0:root:/root:/bin/bash
+bin:x:1:1:bin:/bin:/sbin/nologin
+daemon:x:2:23:daemon:/sbin:/sbin/nologin
+desktop:x:80:80:desktop:/var/lib/menu/kde:/sbin/nologin
+mengqc:x:500:500:mengqc:/home/mengqc:/bin/zsh
+
+ +

上文示例指令会把 passwd 从第三行开始用 bash 作为登录 shell 用户都替换成使用 zsh 登录. 涉及到了 sed 的地址和替换两个知识点, 简言之命令使用 3,$ 约束要处理的数据范围, 然后使用 s 命令执行替换.

+ +

地址介绍

+ +

首先介绍地址相关的概念, 所谓地址是用来表示文件中数据范围的行号或者模式或者特别符号又或者是这些的组合, 比方说上面的 3,$ 就组合了行号和 $ 标记来表示从第三行到文件末尾的范围.

+ +

可能的地址写法有以下几种:

+ +
    +
  1. 仅一个数字 - 表示只处理行号等于数字的这一行
  2. +
  3. first~step 从 first 行开始往后处理, 步长是 step. 也即每隔 step 行处理一次
  4. +
  5. $ 表示最后一行
  6. +
  7. /regexp/ 正则表达式匹配的行
  8. +
  9. \cregexpc 也是正则表达式, 不同的是可以指定 / 变为任意字符, 这样在匹配路径的时候写起来会简单一些
  10. +
  11. m,n 表示从 m 行到 n 行. 如果 m 大于 n, 那么 m 行依然会被匹配一次
  12. +
+ +

GNU Sed 的一些特别支持:

+ +
    +
  1. 0,addr2 几乎和 1,addr2 一样, 区别在于是第一行和 addr2 匹配的时候, 前者只能匹配 1 行, 后者会匹配到文件中部能匹配到 addr2 的地方
  2. +
  3. addr1,+N 匹配从 addr1 开始之后的 N 行
  4. +
  5. addr1,~Naddr1 开始匹配, 每隔 N 行匹配到一次
  6. +
+ +

替换

+ +

s 命令用于执行替换操作, 一般的用法是 s/pattern/content/flag, 其中 pattern 是要替换的模式, content 是要替换的新内容, flag 是给替换命令的一些标记, 比如可以控制替换的次数, 是否忽略大小写等.

+ +

一般而言, pattern 部分是需要用正则表达式表达的, sed 可以支持最多 9 个正则表达式分组(分组就是用 \(\) 包裹起来的部分), 分组可以被反向引用(back references), 反向引用可以出现在两个地方:

+ +
    +
  • 首先是 content 中可以通过 \N 的形式引用某个特定编号的分组(从 1 开始计数, 0 是 pattern 匹配的全部内容)
  • +
  • pattern 中也可以使用反向引用自己的分组, 比方说 \(abc\)\1 可以匹配 abcabc
  • +
+ +

值得一提的是 content 中的 & 字符也可以表示全部匹配的内容.

+ +

flag 是用来微调替换行为的, 常见的 flag 有:

+ +
    +
  1. I 忽略大小写, 不用 i 是因为 i 在 sed 里面是 insert 指令
  2. +
  3. 数字 表示替换第 数字 次出现的 pattern
  4. +
  5. g 全局替换
  6. +
+ +

看一个涉及上面概念的复杂例子:

+ +
> cat passwd | sed 's/\(.*\):x:\(.*\):\2:/\U\1\E ==> &/g'
+ROOT ==> root:x:0:0:root:/root:/bin/bash
+BIN ==> bin:x:1:1:bin:/bin:/sbin/nologin
+daemon:x:2:23:daemon:/sbin:/sbin/nologin
+DESKTOP ==> desktop:x:80:80:desktop:/var/lib/menu/kde:/sbin/nologin
+MENGQC ==> mengqc:x:500:500:mengqc:/home/mengqc:/bin/bash
+
+ +

最后在 content 中可以使用 \U, \L 调整大小写:

+ +
> echo 'ThisIsClassName' | sed 's/^\(.\)/\l\1/;s/\([A-Z]\)/_\l\1/g'
+this_is_class_name
+
+ +

关于替换分隔符:

+ +

sed 的替换分隔符不止可以使用 /, 事实上我们可以用任何字符当做分隔符, 比放在替换路径的场景中, 可以使用 # 当做分隔符:

+ +
> echo '/etc/passwd' | sed 's#/etc#/opt/etc#'
+/opt/etc/passwd
+
+ +

更多

+ +

正则表达式的更多知识:

+ +
    +
  • 开始结束 ^, $
  • +
  • 匹配字符 ., *, \+, \?,
  • +
  • 转义 \
  • +
  • 字符集 [] ~~~ [xxx], [x-y]
  • +
  • \b 单词边界, 可以用来表示单词的开始和结束
  • +
  • 或表达式 |
  • +
  • _{m}
  • +
  • _{m,n}
  • +
  • _{m,}
  • +
+ +

编辑/修改/删除命令

+ +

像 vim 一样, sed 有用于往前插入(insert)内容的 i 命令和往后追加(append)内容的 a 命令和用于修改(change)的 c命令.

+ +
> cat passwd | sed '1asecond line'
+root:x:0:0:root:/root:/bin/bash
+second line
+bin:x:1:1:bin:/bin:/sbin/nologin
+daemon:x:2:23:daemon:/sbin:/sbin/nologin
+desktop:x:80:80:desktop:/var/lib/menu/kde:/sbin/nologin
+mengqc:x:500:500:mengqc:/home/mengqc:/bin/bash
+
+ +
> cat passwd | sed '1ifirst line'
+first line
+root:x:0:0:root:/root:/bin/bash
+bin:x:1:1:bin:/bin:/sbin/nologin
+daemon:x:2:23:daemon:/sbin:/sbin/nologin
+desktop:x:80:80:desktop:/var/lib/menu/kde:/sbin/nologin
+mengqc:x:500:500:mengqc:/home/mengqc:/bin/bash
+
+ +
> cat passwd | sed '1cchange content'
+change content
+bin:x:1:1:bin:/bin:/sbin/nologin
+daemon:x:2:23:daemon:/sbin:/sbin/nologin
+desktop:x:80:80:desktop:/var/lib/menu/kde:/sbin/nologin
+mengqc:x:500:500:mengqc:/home/mengqc:/bin/bash
+
+ +

模式空间和保持空间操作

+ +

保持空间一般用来暂存数据, 通过对模式空间和保持空间的合理安排, 可以实现很多复杂功能.

+ +

g/G 命令是 get 命令的缩写, 表示从保持空间拷贝/追加数据到模式空间, h/H 命令是 hold 命令的缩写, 表示从模式空间拷贝/追加数据到保持空间.

+ +

举一个例子, 把相邻两行合并成一行, 并交换顺序:

+ +
> seq 1 11 | sed -n 'h;n;G;s/\n/ /p'
+2 1
+4 3
+6 5
+8 7
+10 9
+
+ +
+

思考:

+ +
    +
  1. 上面的例子 11 为什么没有打印出来, 想打印的话要怎么做?
  2. +
  3. 上面的问题, 三行应该怎么做?
  4. +
+
+ +

x 命令交换模式空间/保持空间, 还是上面的例子, 假如说不想对调两行的位置了, 可以再用一个 x 命令处理一下:

+ +
> seq 1 11 | sed -n 'h;n;x;G;s/\n/ /p'
+1 2
+3 4
+5 6
+7 8
+9 10
+
+ +

流程跳转

+ +

b 命令用于直接跳转到标号处. 如果不指定标号, 直接跳到脚本结束位置, 开始下次处理循环.

+ +
> cat play
+剧名:白夜追凶
+评分:9.0
+剧名:秘密深林
+评分:9.3
+剧名:权利的游戏第七季
+评分:9.3
+剧名:请回答 1988
+评分:9.6
+
+ +
✗ cat play | sed -n 'N;s/\n/, /;/1988/!b label;s/^/---/;:label;p'
+剧名:白夜追凶, 评分:9.0
+剧名:秘密深林, 评分:9.3
+剧名:权利的游戏第七季, 评分:9.3
+---剧名:请回答 1988, 评分:9.6
+
+ +

不指定标号的例子:

+ +
> cat play | sed -n 'N;s/\n/, /;/1988/!b;s/^/---/;p'
+---剧名:请回答 1988, 评分:9.6
+
+ +

t 命令也是跳转到标号处, 但是有判断之前执行的替换操作是成功的这个前提才会跳转

+ +

下面的例子利用跳转, 全局替换了文本中的 o 字符, 实现了替换命令里面的 g 标志功能.

+ +
> cat passwd | sed ':x;s/o/O/;tx'
+rOOt:x:0:0:rOOt:/rOOt:/bin/bash
+bin:x:1:1:bin:/bin:/sbin/nOlOgin
+daemOn:x:2:23:daemOn:/sbin:/sbin/nOlOgin
+desktOp:x:80:80:desktOp:/var/lib/menu/kde:/sbin/nOlOgin
+mengqc:x:500:500:mengqc:/hOme/mengqc:/bin/bash
+
+ +

再来一个有点绕的:

+ +
> cat play | sed -n 'N;s/\n/, /;:x;/1988/s/^/-/;/----/!tx;p'
+剧名:白夜追凶, 评分:9.0
+剧名:秘密深林, 评分:9.3
+剧名:权利的游戏第七季, 评分:9.3
+----剧名:请回答 1988, 评分:9.6
+
+ +

其他命令

+ +
    +
  1. e 将 pattern space 里面的内容当成命令执行, 并将执行结果当做 pattern space 的内容.
  2. +
  3. *.sed 脚本中的头两个字符如果是 #n, sed 将会自动使用 -n 选项执行
  4. +
  5. 注意 l 命令自带 print 效果
  6. +
  7. = 命令输出行号
  8. +
  9. y 命令用于将两个字符列表按照位置转换(transform)
  10. +
  11. sed 可以一次操作多个文本文件
  12. +
  13. q 在处理完成之后直接退出 sed 程序
  14. +
  15. w 命令用于将模式空间写到文件, r 命令用于将文件读到<标准输出还是模式空间?看上去不像是模式空间>
  16. +
  17. P 多行模式空间的情况下, 仅输出第一行
  18. +
  19. D 多行模式空间的情况下, 仅删除第一行, 且不会将下一行读取到模式空间(对比 d), 并重新从头开始对模式空间内容执行命令
  20. +
+ +

其他

+ +

11 行丢失的问题:

+ +
> {seq 1 11; seq 1 5 | sed 's/^._$/AAAAAA/'} | sed -n 'h;n;G;s/\n/ /p' | sed 's/AAAAAA\s_//g' | sed '/^$/d'
+2 1
+4 3
+6 5
+8 7
+10 9
+11
+
+ +

awk 部分

+ +

Awk 命令的基本格式

+ +
awk -Fs '/pattern/ {action}' input-file
+# OR
+awk -Fs '{action}' intput-file
+# OR
+awk -Fs -f myscript.awk input-file
+
+ +

Example:

+ +
awk -F: '/mail/ {print $1}' /etc/passwd
+
+ +
    +
  1. awk 的程序结构 BEGIN, body, END block
  2. +
+ +

A typical awk program has following three blocks. +BEGIN Block +Syntax of begin block: +BEGIN { awk-commands } +The begin block gets executed only once at the beginning, before awk starts executing the body block for all the lines in the input file. +The begin block is a good place to print report headers, and initialize variables. +You can have one or more awk commands in the begin block. +The keyword BEGIN should be specified in upper case. +Begin block is optional. +Body Block +Syntax of body block: +/pattern/ {action} +The body block gets executed once for every line in the input file. +If the input file has 10 records, the commands in the body block will be executed 10 times (once for each record in the input file). +There is no keyword for the body block. We discussed pattern and action previously. +END Block +Syntax of end block: +END { awk-commands } +The end block gets executed only once at the end, after awk completes executing the body block for all the lines in the input-file. +• The end block is a good place to print a report footer and do any clean-up activities. +• You can have one or more awk commands in the end block. +• The keyword END should be specified in upper case. +• End block is optional. +awk work example Awk work example The following simple example shows the three awk blocks in action. +$ awk ‘BEGIN { FS=”:”;print “—header—” }
+/mail/ {print $1}
+END { print “—footer—”}’ /etc/passwd +—header— +mail +mailnull +—footer— +Note: When you have a very long command, you can either type is on a single line, or split it to multiple lines by specifying a \ at the end of each line. The above example is typed in 3 lines with a \ at the end of line 1 and line 2. In the above example: +BEGIN { FS=”:”;print “—header—” } is the begin block, that sets the field separator variable FS (more on this later), and prints the header. This gets executed only once before the body loop. +/mail/ {print $1} is the body loop, that contains a pattern and an action. i.e. This searches for the keyword “mail” in the input file and prints the 1st field. +END { print “—footer—”}’ is the end block, that prints the footer. +/etc/passwd is the input file. The body loop gets executed for every records in this file. +Instead of executing the above simple example from the command line, you can also execute it from a file. First, create the following myscript.awk file that contains the begin, body, and end loop: +$ vi myscript.awk +BEGIN { +FS=”:” +print “—header—” +} +/mail/ { +print $1 +} + END { + print “—footer—” +} +Next, execute the myscript.awk as shown below for the input file /etc/passwd: +$ awk -f myscript.awk /etc/passwd +—header— +mail +mailnull +—footer— +Please note that a comment inside a awk script starts with #. If you are writing a complex awk script, follow the best practice: write enough comments inside the *.awk file so that it will be easier for you to understand when you look at the file later. Following are some random simple examples that show you various combinations of awk blocks. Only the body block: +awk -F: ‘{ print $1 }’ /etc/passwd +Begin, body, and end block: +awk -F: ‘BEGIN { printf “username\n——\n”}
+{ print $1 }
+END { print “——” }’ /etc/passwd +Begin, and body block: +awk -F: ‘BEGIN { print “UID”} { print $3 }’ /etc/passwd +A Note on using only a BEGIN Block: +Specifying only the begin block is valid awk syntax. When you don’t specify a body loop, there is no point in specifying a input file, since only the body loop gets executed for the lines in the input file. So, use only the BEGIN block when you want to use an awk program to do things not related to file processing. In many of our examples below, we’ll have only the BEGIN block, to explain how some of the awk programming components work. You can use this idea for anything that you see fit. A simple begin only example: +$ awk ‘BEGIN { print “Hello World!” }’ +Hello World! +Multiple Input Files +Please note that you can specify multiple input files. If you specify two input files, first the body block will be executed for all the lines in input-file1, next the body block will be executed for all the lines in input-file2. Multiple input file example: +$ awk ‘BEGIN { FS=”:”;print “—header—” }
+/mail/ {print $1}
+END { print “—footer—”}’ /etc/passwd /etc/group +—header— +mail +mailnull +mail +mailnull +—footer— +Please note that the BEGIN block and the END block will be executed only once, even when you specify multiple input-files.

+
    +
  1. Print Command +By default, the awk print command (without any argument) prints the full record as shown. The following example is equivalent to “cat employee.txt” command. +$ awk ‘{print}’ employee.txt +101,John Doe,CEO +102,Jason Smith,IT Manager +103,Raj Reddy,Sysadmin +104,Anand Ram,Developer +105,Jane Miller,Sales Manager +You can also print specific fields in a record by passing $field-number as a print command argument. The following example is supposed to print only the employee name (field number 2) of every record. +$ awk ‘{print $2}’ employee.txt +Doe,CEO +Smith,IT +Reddy,Sysadmin +Ram,Developer +Miller,Sales +Wait. It didn’t work as expected. It printed from the last name until the end of the record. This is because the default field delimiter in Awk is space. Awk did exactly what we asked; it did print the 2nd field considering space as a delimiter. When the default space is used as delimiter, “101,John” became field-1 and “Doe,CEO” became field- 2 of the 1st record. So, the above awk example printed “Doe,CEO” as field-2. To solve this issue, we should instruct Awk to use comma (,) as field delimiter. Use option -F to indicate the field separator. +awk -F ‘,’ ‘{print $2}’ employee.txt +John Doe +Jason Smith +Raj Reddy +Anand Ram +Jane Miller +When there is only one character used for delimiter, any of the following forms works, i.e. you can specify the field delimiter within single quotes, or double quotes, or without any quotes as shown below. +awk -F ‘,’ ‘{print $2}’ employee.txt +awk -F “,” ‘{print $2}’ employee.txt +awk -F, ‘{print $2}’ employee.txt +Note: You can also use the FS variable for this purpose. We’ll review that in the awk built-in variables section. A simple report that prints employee name and title with a header and footer: +$ awk -F ‘,’ ‘BEGIN
    +{ print “————-\nName\tTitle\n————-“}
    +{ print $2,”\t”,$3;}
    +END { print “————-“; }’ employee.txt
  2. +
+ +
+ +

Name Title

+ +

John Doe CEO +Jason Smith IT Manager +Raj Reddy Sysadmin +Anand Ram Developer +Jane Miller Sales Manager

+ +
+ +

In the above report the fields are not aligned properly. We’ll look at how to do that in later sections. The above example does show how you can use BEGIN to print a header, and END to print a footer. Please note that field $0 represents the whole record. Both of the following examples are the same; each prints the whole lines from employee.txt. +awk ‘{print}’ employee.txt +awk ‘{print $0}’ employee.txt

+
    +
  1. AWK Pattern Matching +You can execute awk commands only for lines that match a particular pattern. For example, the following prints the names and titles of the Managers: +$ awk -F ‘,’ ‘/Manager/ {print $2, $3}’ employee.txt +Jason Smith IT Manager +Jane Miller Sales Manager +The following example prints the employee name whose Emp id is 102: +$ awk -F ‘,’ ‘/^102/ {print “Emp id 102 is”, $2}’ employee.txt +Emp id 102 is Jason Smith
  2. +
]]>
Zhigang Song
硬件/电路/单片机/机器人常用资料2022-10-28T19:34:04+08:002022-10-28T19:34:04+08:00http://sidgwick.github.io/2022/10/28/hardware-document-reference%20copy本文列举了一些常用的硬件数据手册链接和其他比较好的资料, 方便翻阅.

+ +

常用的数据手册

+ + + +

MCU

+ +

Atmel

+ + + +

Espressif

+ + + +

Camera

+ +]]>
Zhigang Song
\ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..dc98a23 --- /dev/null +++ b/index.html @@ -0,0 +1,1317 @@ + + + +主页 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

主页

+

使用 Vue 创建 Web Component

+

准备

+ +

安装 vue 命令行工具:

+ +
> yarn global add @vue/cli
+
+ +

生成一个空白仓库:

+ +
> vue create json-viewer
+
+ +

可以使用的选项如下(参考, 根据自己的需要调整):

+ +

阅读更多

+
+
+

决策树算法

+

算法原理

+ +

树模型

+ +

决策树根节点开始一步步走到叶子节点(决策), 所有的数据最终都会落到叶子节点, 利用决策树既可以做分类也可以做回归.

+ +

树的组成

+ +
    +
  • 根节点: 第一个选择点
  • +
  • 非叶子节点与分支: 中间过程
  • +
  • 叶子节点: 最终的决策结果
  • +
+ +

决策树的训练是从给定的训练集构造(从根节点开始选择特征, 如何进行特征切分)出来一棵树, 然后将测试数据根据构造出来的树模型从上到下去走一遍, 得到决策结果.

+ +

一旦构造好了决策树, 那么分类或者预测任务就很简单了, 只需跟着树走一遍决策流程就可以了, 那么难点就在于如何构造出来一颗树.

+ +

阅读更多

+
+
+

用树回归方法画股票趋势线

+

原文: 机器学习_用树回归方法画股票趋势线

+ +

本篇的主题是分段线性拟合, 也叫回归树, 是一种集成算法, 它同时使用了决策和线性回归的原理, 其中有两点不太容易理解, 一个是决策树中熵的概念, 一个是线性拟合时求参数的公式为什么是由矩阵乘法实现的. 如需详解, 请见前篇:

+ + + +

画出股票的趋势线

+ +

我们常在股票节目里看到这样的趋势线:

+ +

趋势线

+ +

阅读更多

+
+
+

常见的时间序列趋势判别算法

+

本文介绍常见的时间序列趋势判别算法:

+ +
    +
  1. 多项式拟合(斜率)
  2. +
  3. Mann-Kendall 趋势检验检验
  4. +
  5. Cox-stuart 趋势检验
  6. +
+ +

多项式拟合(最小二乘法)

+ +

基本原理

+ +

核心是使用最小二乘法见序列拟合成一条直线, 然后根据直线的斜率 k 判断序列的走势. 如果返回的是正数则正增长, 如果返回的是负数则为下降, 如果为 0 则表示没有趋势.

+ +

阅读更多

+
+
+

Monkey Patching in Go

+
+

本文是 Monkey Patching in Go 的阅读理解.

+
+ +

原文 Monkey Patching in Go

+ +

Many people think that monkey patching is something that is restricted to dynamic languages like Ruby and Python. That is not true however, as computers are just dumb machines and we can always make them do what we want! Let’s look at how Go functions work and how we can modify them at runtime. This article will use a lot of Intel assembly syntax, so I’m assuming you can read it already or are using a reference while reading.

+ +

阅读更多

+
+
+

python 元编程

+

本文讲述了 python 中 class 的一些细节. 包含:

+ +
    +
  1. class 定义和 class 对象
  2. +
  3. class 实例对象
  4. +
+ +

__new__ 函数和 __call__ 函数

+ +

先从简单的开始, 比如以下代码:

+ +

阅读更多

+
+
+

python 的 metaclass 到底是什么

+
+

文章地址: https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python?answertab=votes#tab-top

+
+ +

Classes as objects

+ +

Before understanding metaclasses, it helps to understand Python classes more deeply. Python has a very peculiar idea of what classes are, which it borrows from the Smalltalk language.

+ +

In most languages, classes are just pieces of code that describe how to produce an object. That is somewhat true in Python too:

+ +
>>> class ObjectCreator(object):
+...       pass
+
+>>> my_object = ObjectCreator()
+>>> print(my_object)
+<__main__.ObjectCreator object at 0x8974f2c>
+
+ +

阅读更多

+
+
+
+ + + + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/nbmd/conf.json b/nbmd/conf.json new file mode 100644 index 0000000..98b819d --- /dev/null +++ b/nbmd/conf.json @@ -0,0 +1,6 @@ +{ + "base_template": "base", + "mimetypes": { + "text/markdown": true + } +} \ No newline at end of file diff --git a/nbmd/index.md.j2 b/nbmd/index.md.j2 new file mode 100644 index 0000000..e3c01f8 --- /dev/null +++ b/nbmd/index.md.j2 @@ -0,0 +1,101 @@ +{% extends 'base/display_priority.j2' %} + + +{% block in_prompt %} +{% endblock in_prompt %} + +{% block output_prompt %} +{%- endblock output_prompt %} + +{% block input %} +``` +{%- if 'magics_language' in cell.metadata -%} + {{ cell.metadata.magics_language}} +{%- elif 'name' in nb.metadata.get('language_info', {}) -%} + {{ nb.metadata.language_info.name }} +{%- endif %} +{{ cell.source}} +``` +{% endblock input %} + +{% block error %} +{{ super() }} +{% endblock error %} + +{% block traceback_line %} +{{ line | indent | strip_ansi }} +{% endblock traceback_line %} + +{% block execute_result %} +{% block data_priority scoped %} +{{ super() }} +{% endblock %} +{% endblock execute_result %} + +{% block stream %} +代码输出: + +{{ output.text | indent }} +{% endblock stream %} + +{% block data_svg %} +代码输出: + + {% if "filenames" in output.metadata %} +![svg]({{ output.metadata.filenames['image/svg+xml'] | path2url }}) + {% else %} +![svg](data:image/svg;base64,{{ output.data['image/svg+xml'] }}) + {% endif %} +{% endblock data_svg %} + +{% block data_png %} +代码输出: + + {% if "filenames" in output.metadata %} +![png]({{ output.metadata.filenames['image/png'] | path2url }}) + {% else %} +![png](data:image/png;base64,{{ output.data['image/png'] }}) + {% endif %} +{% endblock data_png %} + +{% block data_jpg %} +代码输出: + + {% if "filenames" in output.metadata %} +![jpeg]({{ output.metadata.filenames['image/jpeg'] | path2url }}) + {% else %} +![jpeg](data:image/jpeg;base64,{{ output.data['image/jpeg'] }}) + {% endif %} +{% endblock data_jpg %} + +{% block data_latex %} +代码输出: + +{{ output.data['text/latex'] }} +{% endblock data_latex %} + +{% block data_html scoped %} +代码输出: + +{{ output.data['text/html'] }} +{% endblock data_html %} + +{% block data_markdown scoped %} +代码输出: + +{{ output.data['text/markdown'] }} +{% endblock data_markdown %} + +{% block data_text scoped %} +代码输出: + +{{ output.data['text/plain'] | indent }} +{% endblock data_text %} + +{% block markdowncell scoped %} +{{ cell.source }} +{% endblock markdowncell %} + +{% block unknowncell scoped %} +unknown type {{ cell.type }} +{% endblock unknowncell %} diff --git a/page2/index.html b/page2/index.html new file mode 100644 index 0000000..e5c3bf7 --- /dev/null +++ b/page2/index.html @@ -0,0 +1,1425 @@ + + + +主页 - 挚爱荒原 + + + + + + + + +
+
+
+ +
+ +
+ +
+ +
+ + + +

主页

+ +
+
+
+
+

sed and awk 101 hacks 笔记

+
+
+
+

图书在线阅读地址: sed and awk 101 hacks

+
+ +

本文记录 *sed and awk 101 hacks* 一书中学到的知识点备忘.

+ +

sed 部分

+ +
+

本文中的 有些知识并不实用, 如果懵, 不用全都学会, 关键是有机会的时候多用一用, 熟能生巧, 慢慢的就都记住了

+ +

!!! 文中的操作不一定是最简洁的操作, 有些为了演示功能生造出来的使用场景.

+
+ +

本文中使用到的示例文件是系统中的密码文件 passwd:

+ +
root:x:0:0:root:/root:/bin/bash
+bin:x:1:1:bin:/bin:/sbin/nologin
+daemon:x:2:23:daemon:/sbin:/sbin/nologin
+desktop:x:80:80:desktop:/var/lib/menu/kde:/sbin/nologin
+mengqc:x:500:500:mengqc:/home/mengqc:/bin/bash
+
+ +
+

阅读更多

+
+ + + + +
+
+

使用 Arduino 和 OV7670(不含 FIFO) 拍摄图像

+
+
+
+

本文章是对 Arduino Camera (OV7670) Tutorial 的一片不原汁原味的翻译. +中文是按照我自己的理解写出来的, 更多的当个笔记. 如果有错误的地方请参考英文原文.

+
+ +

所需硬件

+ +
    +
  1. OV7670 摄像头模块(不需要 FIFO)
  2. +
  3. Arduino Uno R3
  4. +
  5. 面包板以及连接线
  6. +
  7. USB 打印口线(Arduino 通过 USART 向 PC 发送拍摄到的图片)
  8. +
+ +

简述

+ +

在运动检测, 障碍物识别, 无人机和机器人领域, 经常会需要使用摄像头来捕获图像. 在这些场景中, 因为 Arduino 羸弱的性能不足以处理图片和视频, 比较好的方案是使用 Raspberry Pi 或者 BeagleBone Black. 然而如果项目不需要高分辨率图像, 那么 OV7670 模块可能也适合. 本片教程介绍此相机, 并教你如何获取一张 VGA 尺寸(大约应该是 640x480 大小)图像.

+ +
+

阅读更多

+
+ + + + +
+
+

Python 虚拟环境管理 virtualenvwrapper 相关使用

+
+
+
+

https://stackoverflow.com/questions/49470367/install-virtualenv-and-virtualenvwrapper-on-macos +Virtualenv

+
+ +

To install virtualenv and virtualenvwrapper for repetitive use you need a correctly configured Python (this example uses Python 3.x but process is identical for Python 2.x).

+ +

Although you can get python installer from Python website I strongly advice against it. The most convenient and future-proof method to install Python on MacOS is brew.

+ +
+

阅读更多

+
+ + + + +
+
+

Mac 下 Homebrew 相关使用

+
+
+

Homebrew 操作介绍

+ +

网上的更新国内源大多不完整,导致 brew update 失败 +先更新下 brew +有时 brew 版本太旧也会有问题

+ +
+

阅读更多

+
+ + + + +
+
+

如何阅读 Arduino 原理图

+
+
+
+

英文原文: https://learn.circuit.rocks/the-basic-arduino-schematic-diagram

+
+ +

+ +

Get deeper into the Arduino craft by looking into a reference design. In here I will get into details with the basic arduino schematic diagram using one of their more popular development board, the Arduino UNO.

+ +
+

阅读更多

+
+ + + + +
+
+
+
+
+ + + + +
+
+ + +
+
+ +
+
+ +
+ + + + + + +
+ + + + diff --git a/page3/index.html b/page3/index.html new file mode 100644 index 0000000..f48db5f --- /dev/null +++ b/page3/index.html @@ -0,0 +1,1393 @@ + + + +主页 - 挚爱荒原 + + + + + + + + +
+
+
+ +
+ +
+ +
+ +
+ + + +

主页

+ +
+
+
+
+

移位寄存器

+
+
+
+

英文原文: https://dronebotworkshop.com/shift-registers/

+
+ +

I’ve got a few shifty characters with me today but don’t worry, I’ll show you how to control them and expand the capabilities of your Arduino. And, as a bonus, we’ll build a fancy LED light display.

+ +

介绍

+ +

Today we will work with a couple of basic electronics “building blocks”, shift registers. These handy devices are used for all sorts of purposes like data conversion, buffering and storage, but today we will be seeing how they can also be used to expand the number of digital I/O ports on an Arduino or other microcontrollers.

+ +

+ +
+

阅读更多

+
+ + + + +
+
+

Backtrader 学习系列 - QuickStart

+
+
+
+

参考原文: https://www.backtrader.com/docu/quickstart/quickstart

+ +

IMPORTANT: The data files used in the quickstart guide are updated from time to time, which means that the adjusted close changes and with it the close (and the other components). That means that the actual output may be different to what was put in the documentation at the time of writing.

+
+ +

平台使用

+ +

首先粗略地解释使用 backtrader 时的 2 个基本概念, 然后来看一系列从空跑直到几乎完整的策略的例子.

+ +
    +
  1. +

    Lines

    + +

    Data Feeds, Indicators 以及 Strategy 拥有 lines. 所谓的 line 就是一些列的点组成的线, 通常 Data Feed 有以下几条 line: Open, High, Low, Close, Volume, OpenInterest. 举例来说 Open 点随时间移动可以组成一条 line, 其他几个指标也是如此. 因此一个 Data Feed 通常有 6 条 line. 考虑 DateTime 的话(DateTime 是 backtrader DataFeed 的索引), 那就有 7 条 line.

    +
  2. +
  3. +

    索引 0

    + +

    当访问 line 上面的数据时, 当前值得索引是 0, 访问上一个值使用索引 -1. Python 语言中 -1 一般用来表示一个可迭代对象的最后一个值, 我们这里用 -1 来表示最后一个 output 值(这里的 output 应该就是已经处理过的数据列表)

    +
  4. +
+ +

现在想像一下我们在策略初始化的时候创建一条移动平均线(Simple Moving Average):

+ +
+

阅读更多

+
+ + + + +
+
+

比特币科普

+
+
+

历史

+ +

2008 年, 中本聪(名字像日本人, 但是到现在为止我们仍然不知道中本聪到底是谁)发表了一篇文章, 标题是 “Bitcoin:A Peer To Peer Electronic Cash System”, 在这篇文章里, 他描述比特币基本的原理. 这篇文章发表出来以后, 在社区里激起了广大的反响. 2009 年, 基于这个技术的比特币正式诞生.

+ +

比特币诞生之后很长一段时间里, 它都是极客玩家在玩. 2009 年 1 月 12 日, 哈尔·芬尼(芬尼是计算机科学家,比特币先锋)和中本聪之间进行了第一笔比特币交易. 2010 年 5 月 22 日, 有人用一万个比特币点了一份价值 $25 的批萨外卖…. 之后的几年时间里, 越来越多的人接触到这个新奇的东西, 越来越多的人开始接受它, 它的价格也被炒的越来越高. 最近一段时间的均价在 $11000 左右(本周暴跌, 我有杠杆还重仓…真是投资砖家:disappointed:).

+ +
+

阅读更多

+
+ + + + +
+
+

Python 官方文档扩展部分笔记

+
+
+

概述

+ +

文件名

+ +

如果模块的名字是 spam, 那么我们的模块文件名字一般回叫做 spammodule.c, 但是对于一些比较长的模块名字, 也可以直接使用模块名来命名, 比如像 spammify.

+ +
+

阅读更多

+
+ + + + +
+
+

json c 笔记

+
+
+

JSON-C 使用 json_object 结构体来表示 json +对象, 可以使用 json_object_to_json_string_ext 函数来输出一个 json 对象. +json_object_to_json_string 函数是前一个函数的包装.

+ +
+

阅读更多

+
+ + + + +
+
+

Redux高级用法

+
+
+

Logger

+ +

开发过程中, 如果能自动打印每次发送的 action 以及 reducers +处理前后的状态树, 那么对程序的调试是很有帮助的.

+ +

社区提供了一个这样的库, 叫做 redux-logger, 它的使用方法如下:

+ +
+

阅读更多

+
+ + + + +
+
+

Redux学习笔记

+
+
+

基本概念

+ +

Action

+ +

action 是 Redux store 的唯一获取信息的地方. 它用于从应用向 store 传递数据.

+ +
+

阅读更多

+
+ + + + +
+
+
+
+
+ + + + +
+
+ + +
+
+ +
+
+ +
+ + + + + + +
+ + + + diff --git a/page4/index.html b/page4/index.html new file mode 100644 index 0000000..12c5f5a --- /dev/null +++ b/page4/index.html @@ -0,0 +1,1246 @@ + + + +主页 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

主页

+

雨天

+

一月份的上海雨天
+离职,天灰蒙蒙,心情也阴沉
+忘不了朋友送我的一根烟

+ +

阅读更多

+
+
+

Makefile 相关

+

Makefile 里面的变量

+ +
    +
  • $< 第一个依赖
  • +
  • $^ 全部依赖
  • +
  • $@ 目标文件
  • +
  • $* ???
  • +
+ +

阅读更多

+
+
+

React基本教程

+

目标

+ +

我们将创建一个小但是很实用的评论框.

+ +

功能如下:

+ +
    +
  1. 查看所有的评论
  2. +
  3. 提交评论的表单
  4. +
  5. 实现自定义后端的钩子
  6. +
+ +

阅读更多

+
+
+

Yii2学习笔记 -- 构建一个自定义的应用

+

这次, 我们来实现一个小小的 web 应用. 我们将用绝对符合软件工程的方法来实现.

+ +

设计阶段

+ +

我们的任务

+ +

假想我们是一个提供某种服务的小商户. 我们的客户数量很多, 所以, 用纸张来存储客户 +信息会显得有点笨重. 所以, 我们需要一种能接受给定信息, 反馈出客户全部信息的自动 +查询方法.

+ +

阅读更多

+
+
+

Yii2学习笔记 -- 开始学习

+

可以使用 composer 来安装 yii2 的官方基本模板或者高级模板. 末班里面带了一些实例, 可 +以作为入门学习.

+ +

使用基本模板

+ +
composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic
+
+ +

上面的 shell 语句会帮助我们自动的安装 yii2 框架, 并处理依赖关系.

+ +

阅读更多

+
+
+

利用Graphviz Dot绘图

+
+

转自: http://www.cnblogs.com/sld666666/archive/2010/06/25/1765510.html

+
+ +

Graphviz 介绍

+ +

Graphviz 是大名鼎鼎的贝尔实验室的几位牛人开发的一个画图工具. 它的理念和一般 +的”所见即所得”的画图工具不一样, 是”所想即所得”. Graphviz 提供了 dot 语言来编写绘 +图脚本. 什么?! 画个图也需要一个语言!! 不要急, dot 语言是非常简单地, 只要看了下 +面几个例子, 就能使用了.

+ +

阅读更多

+
+
+
+ + + + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/page5/index.html b/page5/index.html new file mode 100644 index 0000000..c4bb798 --- /dev/null +++ b/page5/index.html @@ -0,0 +1,1255 @@ + + + +主页 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

主页

+

setjmp和longjmp函数使用详解

+
+

转载文章, 不知道原始出处了.

+
+ +

非局部跳转语句setjmplongjmp函数.

+ +

非局部指的是, 这不是由普通 C 语言 goto, 语句在一个函数内实施的跳转, 而是在栈上跳 +过若干调用帧, 返回到当前函数调用路径上的某一个函数中.

+ +

阅读更多

+
+
+

Yii2 AJAX Form Submission

+

Yii2 provides very nice functionality to create forms and handle client and +server side validation. It creates client side validation rules automatically +from rules provided in Model class. Yii also provides its own Javascript +functionality to validate and submit forms.

+ +

阅读更多

+
+
+

YII2使用Pjax无刷新加载页面

+

pjax is a jQuery plugin that allows quick website navigation by combining ajax +and pushState. It works by sending a special request to the server every time +a link is clicked or a form is submitted. The server sends back the content +that needs to be updated, pjax replaces old content with new content and +pushes the new URL to browser history, without the need to update the whole +page.

+ +

阅读更多

+
+
+

Memcached线程安全函数列表

+
/*
+ * Pulls a conn structure from the freelist, if one is available.
+ */
+conn *mt_conn_from_freelist() {
+    conn *c;
+
+    pthread_mutex_lock(&conn_lock);
+    c = do_conn_from_freelist();
+    pthread_mutex_unlock(&conn_lock);
+
+    return c;
+}
+
+ +

阅读更多

+
+
+

Memcached多线程分析

+

从 1.2.2 版本开始, memcached 引入了多线程. 这里我们讨论的就是第一个多线程版本. +关于这个版本的多线程, 实现的其实很粗糙, 锁的粒度都很大.

+ +

说一下这里的多线程相关函数, 我觉得我自己就够水笔了, 如果你看不懂这些调用, 就去看书吧…

+ +

相关的数据结构

+ +

多线程用的的一些数据结构, 这里的 CQ 即是Connect Queue的缩写.

+ +

下面这个结构体, 定义了 connection 队列里面的一些列套接字和与之相关的信息.

+ +

阅读更多

+
+
+

Memcached的哈希表

+

Memcached 在收到 key 时, 会把它哈希到自己的哈希表里面去. 这篇文章分析哈希表.

+ +

首先说一下 memcached 用到的哈希算法, 是一个叫做 Bob Jenkins 的牛人提出的, 代码也是 +这位高人写得, 这里提供他的网页链接

+ +

剔除掉这位高手写得哈希算法, memcached 实际上在这里没做多少事, 主要就是实现了哈 +希表的管理

+ +

阅读更多

+
+
+

Memcached的数据管理

+

Memcached 在收到数据时, 是以 item 的形式存放到内存里的. 这些 item 之间组成链表

+ +

item 的分配

+ +

老样子, 在源码之前, 数据结构要弄个差不多. 那么, 先来看一下 item 的结构吧.

+ +

阅读更多

+
+
+
+ + + + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/page6/index.html b/page6/index.html new file mode 100644 index 0000000..3c7727c --- /dev/null +++ b/page6/index.html @@ -0,0 +1,1260 @@ + + + +主页 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

主页

+

Memcached的事件处理函数

+

我们知道, Memcached 使用了 Libevent 库来驱动. 它用 Libevent 实现了程序自己的时钟, +以及 todelete 表(过期内容表)里面的删除. 时钟和过期删除机制在 +Memcached 初始化一节已经讨论过了. 本篇讨论更核心的, 即它的客 +户端连接请求处理.

+ +

阅读更多

+
+
+

Memcached的slabs(内存管理)

+

初始化内存管理部分. +Memcached 是按照页面来管理它使用的内存的. 这样做的好处是可以减少每次都新申请内 +存的malloc调用, 但是不可避免的产生了内存空间浪费. 本篇分析 Memcached 的内存管 +理机制

+ +

阅读更多

+
+
+

Memcached套接字的初始化

+

之所以把套接字初始化从Memcached 的初始化过程拿出来, 主要考虑 +到那文章已经很长了, 再者, Memcached 一共用到了 3 种套接字(即: TCP, UDP 和 NUIX 域套 +接字), 单独拿出来说, 顺便做一个对比, 更能深入地帮助我们理解这三种套接字之间的相 +同点与不同点.

+ +

阅读更多

+
+
+

Vim as a PHP IDE

+

这篇文章内容基本上是一个老外写的, 我读完了感觉不错, 遂自己做了点笔记. 附上 +原文链接

+ +

代码风格检查

+ +

这里用到了 PHP CodeSniffer, 可以从 PEAR 安装它, 命令如下

+ +
pear install PHP_CodeSniffer
+
+ +

阅读更多

+
+
+

Memcached 的初始化过程

+

version 1.2.0

+ +

整体大概分析

+ +
main 函数变量声明
+ +
int c;
+struct in_addr addr; /* TCP监听地址 */
+bool lock_memory = false; /* 是否进行内存锁定 */
+bool daemonize = false; /* 是否守护进程 */
+int maxcore = 0; /* core dump文件最大容量 */
+char *username = NULL; /* 用户名 */
+char *pid_file = NULL; /* pid文件路径 */
+struct passwd *pw;
+struct sigaction sa;
+struct rlimit rlim;
+
+ +

阅读更多

+
+
+

如何阅读源代码

+

原文: https://wiki.c2.com/?TipsForReadingCode

+ +

Tips For Reading Code

+ +

提高编程技巧的一个重要方法就是阅读伟大的程序([ReadGreatPrograms]). 诸如 +[SelfDocumentingCode]以及[LiterateProgramming]技术使得写出的代码更加容易阅读.

+ +

然而, 多数时候我们阅读的代码没有实现上面的标准. 那么, 有什么好办法能使得我们必 +须要理解或者学习的这种巨大的, 不结构化的, 由数十人维护的, 内部不一致的, 没有注 +释的代码更直观呢?

+ +

阅读更多

+
+
+
+ + + + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/page7/index.html b/page7/index.html new file mode 100644 index 0000000..3f51f47 --- /dev/null +++ b/page7/index.html @@ -0,0 +1,1215 @@ + + + +主页 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

主页

+

Javascript函数默认参数

+

php 有个很方便的用法是在定义函数时可以直接给参数设默认值,如:

+ +
function simue ($a=1,$b=2){
+    return $a+$b;
+}
+echo simue(); //输出3
+echo simue(10); //输出12
+echo simue(10,20); //输出30
+
+ +

阅读更多

+
+
+
+ + + + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/page8/index.html b/page8/index.html new file mode 100644 index 0000000..052e028 --- /dev/null +++ b/page8/index.html @@ -0,0 +1,1224 @@ + + + +主页 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

主页

+

PHP数组总结

+

在数组里面插入值

+ +
array array_splice(array &$input, int $offset [, int $length = 0 [, mixed $replacement ]])`
+
+ +

这个函数本来是用于在数组里面删除并替换一部分内容的, 当我们删掉的内容为空, +同时又有$replacement参数时, 就产生了插入的效果.

+ +

阅读更多

+
+
+

PHP对象传递, 赋值

+

按值传递和引用传递

+ +

对象赋值是引用赋值, 这点应该特别注意

+ +
$a = $obj;
+$obj->name = "bar";
+$a->name = "foo";
+echo $obj->name; // output is foo
+
+ +

阅读更多

+
+
+

YiiBase小记

+

总说

+ +

YiiBase 是 Yii 的基础包装, 可以理解成司令官的角色. 它用于自动加载, 创建别名, +把别名转换为地址, 翻译以及其他一系列操作.

+ +

阅读更多

+
+
+

YII2的Component总结

+

Yii2 官方仓库, Qiang Xue 第一次提交, commit: fc7c1f…. 虽然没看过 Yii1 的代码, +估计是扒过来的, 因为正式发布的版本和这次提交大不一样. 这里扣出来这个原始版本, +体会一下其中的思想.

+ +

阅读更多

+
+
+

YII2的Behavior总结

+

Yii2 官方仓库, Qiang Xue 第一次提交, commit: fc7c1f…. 虽然没看过 Yii1 的代码, 但是还是觉得是扒过来的, +因为正式发布的版本和这次提交大不一样. 这里扣出来这个原始版本, 体会一下其中的思想.

+ +

阅读更多

+
+
+

SQL语句里面的join

+

对于 SQL 中inner join, outer joincross join的区别很多人不知道, 我也是别人问起, 才查找资料看了下, 跟自己之前的认识差不多. +如果你使用 join 连表, 默认的情况下是inner join. 另外, 开发中使用的left joinright join属于outer join, outer join还包括full join. +下面我通过图标让大家认识它们的区别. 现有两张表, Table A 是左边的表, Table B 是右边的表. 其各有四条记录, 其中有两条记录 name 是相同的:

+ +

阅读更多

+
+
+

Python文本处理

+

整理自网上, 东拼西凑加上自己的理解组合的.

+ +

关于文本处理

+ +

我们谈到”文本处理”时, 我们通常是指处理的内容. Python 将文本文件的内容读入可以操作的字符串变量非常容易. +文件对象提供了三个”读”方法

+ +

阅读更多

+
+
+
+ + + + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/page9/index.html b/page9/index.html new file mode 100644 index 0000000..b712ff5 --- /dev/null +++ b/page9/index.html @@ -0,0 +1,1256 @@ + + + +主页 - 挚爱荒原 + + + + + + + + +
+
+ +
+ +
+ +
+ + +

主页

+

GNU Autotools基础知识

+

在 Linux 下写 C 程序, 这套著名的工具包还是要了解一点的. 在网上查了不少东西, 现在挑重点的记录一下.

+ +

Autotools 简介

+ +

autotools 时一套构建系统(build system), 用于把人从写 makefile 的任务里面解放出来. 这套系统有下面五个部分组成

+ +

阅读更多

+
+
+

C语言函数的可变参数

+

关于可变参数

+ +

大家对printf函数都不陌生, 它的第一个参数是格式化控制字符串, 后面是要打印的值 +打印值可以有任意多个, 这就是所谓的可变参数.

+ +

先看printf的函数原型(man 3 printf)

+ +

阅读更多

+
+
+

PHP内核, 开始前的准备

+

代码阅读器

+ +

选用 Vim, 外加 ctags 和 cscope, 爽到爆

+ +

C 语言的几点复习

+ +

宏定义里面的#

+ +

宏定义里面的#表示在字符串前后加上双引号, 比如下面的代码

+ +

阅读更多

+
+
+

PHP内核探索

+

问, 为什么要读 PHP 源码?

+ +

个人读 PHP 源码纯粹是爱好, 顺便巩固自己学的 Linux C 编程知识, 当然了, 也为了在聊天 +室聊天的时候能显得比较牛逼… :)

+ +

阅读更多

+
+
+

Shell中的字符串操作

+

:=:-操作符

+ +
# 当a值存在时, :=与:-操作效果一样
+$ a='bc';str=${a:=aaa};echo $str, $a
+bc, bc
+$ a='bc';str=${a:-aaa};echo $str, $a
+bc, bc
+
+# str与a都被赋值
+$ str=${a:=aaa};echo $str, $a
+aaa, aaa
+
+# 注意下面, a没有被赋值
+$ str=${a:-aaa};echo $str, $a
+aaa,
+
+ +

阅读更多

+
+
+

Jekyll的基本用法

+

什么是 jekyll??

+ +

简单说: 它是静态网页生成器. 具体点: 包含一些用 markdown 语法写的文件的文件夹;通 +过 markdown 和 Liquid 转换器生成一个网站. 同时 jekyll 也是 github 静态页面引擎. 意味着 +你可以用 jekyll 生成你的页面, 免费托管在 github 服务器.

+ +

阅读更多

+
+
+

一些知识点

+

安全

+ +
    +
  • 宽字符注入攻击
  • +
+ +

VIM

+ +
    +
  • Vim 折叠代码的用法
  • +
+ +

C 语言

+ +
    +
  • strtok函数详解
  • +
+ +

阅读更多

+
+
+
+ + + + +
+ + +
+
+
+ + + + + + +
+ + + + diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..305e2cf --- /dev/null +++ b/robots.txt @@ -0,0 +1 @@ +Sitemap: http://sidgwick.github.io/sitemap.xml diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..49090c3 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,324 @@ + + + +http://sidgwick.github.io/2015/08/01/knowledges.html +2015-08-01T18:28:04+08:00 + + +http://sidgwick.github.io/2015/08/02/jekyll-basic-usage.html +2015-08-02T19:28:04+08:00 + + +http://sidgwick.github.io/2015/08/02/shell-string.html +2015-08-02T19:28:04+08:00 + + +http://sidgwick.github.io/2015/08/03/init.html +2015-08-03T05:28:04+08:00 + + +http://sidgwick.github.io/2015/08/05/Supercolliding-a-PHP-array.html +2015-08-05T00:00:00+08:00 + + +http://sidgwick.github.io/2015/08/07/prepare.html +2015-08-07T03:28:04+08:00 + + +http://sidgwick.github.io/2015/08/09/varying-number-of-arguments.html +2015-08-09T19:34:04+08:00 + + +http://sidgwick.github.io/2015/08/10/gnu-autotools.html +2015-08-10T20:14:04+08:00 + + +http://sidgwick.github.io/2015/08/11/python-file-read.html +2015-08-11T20:14:04+08:00 + + +http://sidgwick.github.io/2015/08/12/sql-join.html +2015-08-12T02:28:04+08:00 + + +http://sidgwick.github.io/2015/08/18/YII2-Behavior.html +2015-08-18T03:28:04+08:00 + + +http://sidgwick.github.io/2015/08/18/YII2-component.html +2015-08-18T04:28:04+08:00 + + +http://sidgwick.github.io/2015/08/21/Yii2-YiiBase.html +2015-08-21T03:28:04+08:00 + + +http://sidgwick.github.io/2015/08/21/PHP-object-pass-by-referance.html +2015-08-21T20:28:04+08:00 + + +http://sidgwick.github.io/2015/08/22/PHP-array.html +2015-08-22T23:28:04+08:00 + + +http://sidgwick.github.io/2015/08/25/Yii-Model.html +2015-08-25T04:28:04+08:00 + + +http://sidgwick.github.io/2015/08/25/Yii-Vector-Dictionary.html +2015-08-25T04:28:04+08:00 + + +http://sidgwick.github.io/2015/09/10/Javascript-default-arguments.html +2015-09-10T17:28:04+08:00 + + +http://sidgwick.github.io/2015/09/10/Javascript-variables-type.html +2015-09-10T17:28:04+08:00 + + +http://sidgwick.github.io/2015/09/10/C-io-fcntl.html +2015-09-10T19:28:04+08:00 + + +http://sidgwick.github.io/2015/09/11/libevent-tiny-introduction.html +2015-09-11T19:34:04+08:00 + + +http://sidgwick.github.io/2015/09/14/Memcached-earliest-version.html +2015-09-14T20:28:04+08:00 + + +http://sidgwick.github.io/2015/09/17/memory-alignment.html +2015-09-17T20:34:04+08:00 + + +http://sidgwick.github.io/2015/09/17/sql-on-duplicate-key-update.html +2015-09-17T23:28:04+08:00 + + +http://sidgwick.github.io/2015/09/19/how-to-read-source-code.html +2015-09-19T18:28:04+08:00 + + +http://sidgwick.github.io/2015/09/26/APUE-buffer.html +2015-09-26T19:34:04+08:00 + + +http://sidgwick.github.io/2015/10/16/memcached-init.html +2015-10-16T19:28:04+08:00 + + +http://sidgwick.github.io/2015/10/17/craftyjs-getting-start.html +2015-10-17T17:28:04+08:00 + + +http://sidgwick.github.io/2015/10/31/vim-as-a-php-ide.html +2015-10-31T20:28:04+08:00 + + +http://sidgwick.github.io/2015/11/04/Memcached-socket-init.html +2015-11-04T08:28:04+08:00 + + +http://sidgwick.github.io/2015/11/05/Memcached-slabs.html +2015-11-05T08:28:04+08:00 + + +http://sidgwick.github.io/2015/11/06/Memcached-event-handler.html +2015-11-06T06:28:04+08:00 + + +http://sidgwick.github.io/2015/11/07/Memcached-item.html +2015-11-07T20:28:04+08:00 + + +http://sidgwick.github.io/2015/11/08/Memcached-assoc-xxx.html +2015-11-08T06:28:04+08:00 + + +http://sidgwick.github.io/2015/11/13/Memcached-threads.html +2015-11-13T20:28:04+08:00 + + +http://sidgwick.github.io/2015/11/14/Memcached-locks.html +2015-11-14T18:28:04+08:00 + + +http://sidgwick.github.io/2015/11/28/yii2-pjax.html +2015-11-28T18:28:04+08:00 + + +http://sidgwick.github.io/2015/11/28/YII2-AJAX-Form-Submission.html +2015-11-28T19:28:04+08:00 + + +http://sidgwick.github.io/2015/11/30/convert-man-page-to-pdf.html +2015-11-30T23:28:04+08:00 + + +http://sidgwick.github.io/2015/12/25/jump-function.html +2015-12-25T04:28:04+08:00 + + +http://sidgwick.github.io/2015/12/26/graphviz-dot.html +2015-12-26T18:28:04+08:00 + + +http://sidgwick.github.io/2015/12/26/python-grammer.html +2015-12-26T20:14:04+08:00 + + +http://sidgwick.github.io/2015/12/30/yii2-getting-start.html +2015-12-30T18:28:04+08:00 + + +http://sidgwick.github.io/2015/12/30/yii2-making-custom-app-with-yii2.html +2015-12-30T22:28:04+08:00 + + +http://sidgwick.github.io/2016/01/06/reactjs-tutorial.html +2016-01-06T17:28:04+08:00 + + +http://sidgwick.github.io/2016/04/14/makefile.html +2016-04-14T22:28:04+08:00 + + +http://sidgwick.github.io/2016/08/16/raining-day.html +2016-08-16T16:14:04+08:00 + + +http://sidgwick.github.io/2017/01/09/learning-mongodb.html +2017-01-09T19:28:04+08:00 + + +http://sidgwick.github.io/2017/02/07/Redux.html +2017-02-07T17:28:04+08:00 + + +http://sidgwick.github.io/2017/02/11/Redux-advance.html +2017-02-11T17:28:04+08:00 + + +http://sidgwick.github.io/2017/02/26/lisp-basic.html +2017-02-26T05:28:04+08:00 + + +http://sidgwick.github.io/2017/03/10/json-c.html +2017-03-10T19:28:04+08:00 + + +http://sidgwick.github.io/2017/05/18/python-official-extension-tutorial.html +2017-05-18T20:14:04+08:00 + + +http://sidgwick.github.io/2018/01/24/bitcoin-note.html +2018-01-24T04:28:04+08:00 + + +http://sidgwick.github.io/2022/07/09/backtrader-quick-start.html +2022-07-09T05:34:04+08:00 + + +http://sidgwick.github.io/2022/08/13/shift-register.html +2022-08-13T19:34:04+08:00 + + +http://sidgwick.github.io/2022/08/31/arduino-schematic.html +2022-08-31T09:34:04+08:00 + + +http://sidgwick.github.io/2022/08/31/avr-gcc-tutorial.html +2022-08-31T09:34:04+08:00 + + +http://sidgwick.github.io/2022/10/12/homebrew.html +2022-10-12T18:28:04+08:00 + + +http://sidgwick.github.io/2022/10/12/letsencrypt.html +2022-10-12T18:28:04+08:00 + + +http://sidgwick.github.io/2022/10/12/virtualenv.html +2022-10-12T18:28:04+08:00 + + +http://sidgwick.github.io/2022/10/28/hardware-document-reference-copy.html +2022-10-28T19:34:04+08:00 + + +http://sidgwick.github.io/2022/10/28/arduino-ov7670.html +2022-10-28T19:34:04+08:00 + + +http://sidgwick.github.io/2023/03/24/sed-and-awk-101-hacks.html +2023-03-24T10:28:04+08:00 + + +http://sidgwick.github.io/2023/04/14/metaclass.html +2023-04-14T10:28:04+08:00 + + +http://sidgwick.github.io/2023/04/14/meta-programming.html +2023-04-14T10:28:04+08:00 + + +http://sidgwick.github.io/2023/06/29/monkey-patching-in-go.html +2023-06-29T22:28:04+08:00 + + +http://sidgwick.github.io/2023/09/15/time-sequence-trending-algorithm.html +2023-09-15T19:34:04+08:00 + + +http://sidgwick.github.io/2023/09/17/linear-regression-decision-tree-trending.html +2023-09-17T05:34:04+08:00 + + +http://sidgwick.github.io/2023/09/20/decison-tree.html +2023-09-20T05:14:41+08:00 + + +http://sidgwick.github.io/2024/01/25/vue-web-component.html +2024-01-25T03:00:00+08:00 + + +http://sidgwick.github.io/2024/02/27/Machine-Learning-Mathematical-Foundations-hw2.html +2024-02-27T19:14:41+08:00 + + +http://sidgwick.github.io/about.html + + +http://sidgwick.github.io/archive.html + + +http://sidgwick.github.io/ + + +http://sidgwick.github.io/page2/ + + +http://sidgwick.github.io/page3/ + + +http://sidgwick.github.io/page4/ + + +http://sidgwick.github.io/page5/ + + +http://sidgwick.github.io/page6/ + + +http://sidgwick.github.io/page7/ + + +http://sidgwick.github.io/page8/ + + +http://sidgwick.github.io/page9/ + + diff --git a/x b/x new file mode 100644 index 0000000..e61ef7b --- /dev/null +++ b/x @@ -0,0 +1 @@ +aa