mirror of
https://github.com/eiblog/eiblog.git
synced 2026-02-09 16:12:26 +08:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc37d5e093 | ||
|
|
61024bfebd | ||
|
|
f20c4a6063 | ||
|
|
c24e6bf7bd | ||
|
|
ade94168d3 | ||
|
|
552d010650 | ||
|
|
1c3106cbb0 | ||
|
|
168937f1b2 | ||
|
|
730cffcb5b | ||
|
|
8c3f1c2aba | ||
|
|
ea375ea76c | ||
|
|
275a6c0c31 | ||
|
|
360204995d | ||
|
|
c9fc0cc75a | ||
|
|
41daaa322e | ||
|
|
894535fbe5 | ||
|
|
6fc5af1b0f | ||
|
|
5ce806a7d7 | ||
|
|
25cb23fdb3 | ||
|
|
a89a1a2bc9 | ||
|
|
93e170f9ac | ||
|
|
59d9a616aa | ||
|
|
2ff0934206 | ||
|
|
cde7cba2f0 | ||
|
|
2be7501afe | ||
|
|
487d35dae2 | ||
|
|
19af9376cb | ||
|
|
3ddd2a0b33 | ||
|
|
ee7523b124 | ||
|
|
cc1dbac1f0 | ||
|
|
04532ba8a6 | ||
|
|
0a2a132b11 | ||
|
|
3ff712d407 | ||
|
|
27162d2205 | ||
|
|
f150974566 | ||
|
|
b94fc825b3 | ||
|
|
d8f0e30285 | ||
|
|
e0a5f0ebca | ||
|
|
c18d9c0bef | ||
|
|
e1ec5cd08a | ||
|
|
5efdd72e58 | ||
|
|
b9470fa14c | ||
|
|
a932d2906d | ||
|
|
3ff5977941 | ||
|
|
da7b726e8d | ||
|
|
3923bc70f1 | ||
|
|
8dc73fd67c | ||
|
|
2825bbfeae | ||
|
|
66811830b0 | ||
|
|
c4bf59ce5d | ||
|
|
6cea283f86 | ||
|
|
3992db49ba | ||
|
|
055c2307cb | ||
|
|
11b0f486cd | ||
|
|
ed0a50b626 | ||
|
|
cf2b2d6d34 | ||
|
|
00456806bb |
@@ -1,18 +1,16 @@
|
|||||||
.git
|
.git
|
||||||
|
conf
|
||||||
vendor
|
vendor
|
||||||
setting
|
setting
|
||||||
conf
|
|
||||||
static
|
|
||||||
views
|
|
||||||
docs
|
docs
|
||||||
!static/tzdata
|
static/*.*
|
||||||
|
!static/favicon.ico
|
||||||
|
**/.DS_Store
|
||||||
Dockerfile
|
Dockerfile
|
||||||
glide.yaml
|
glide.yaml
|
||||||
glide.lock
|
glide.lock
|
||||||
*.yml
|
*.yml
|
||||||
*.md
|
|
||||||
*.go
|
*.go
|
||||||
*.sh
|
*.sh
|
||||||
*.DS_Store
|
|
||||||
.gitignore
|
.gitignore
|
||||||
.dockerignore
|
.dockerignore
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,4 +1,4 @@
|
|||||||
*.DS_Store
|
**/.DS_Store
|
||||||
*.exe
|
*.exe
|
||||||
conf/ssl/domain.*
|
conf/ssl/domain.*
|
||||||
eiblog
|
eiblog
|
||||||
|
|||||||
21
.travis.yml
21
.travis.yml
@@ -1,37 +1,26 @@
|
|||||||
sudo: required # 超级权限
|
sudo: required # 超级权限
|
||||||
|
|
||||||
dist: trusty # 在ubuntu:trusty
|
dist: trusty # 在ubuntu:trusty
|
||||||
|
|
||||||
language: go # 声明构建语言环境
|
language: go # 声明构建语言环境
|
||||||
|
|
||||||
go: # 只构建最新版本
|
go: # 只构建最新版本
|
||||||
- 1.8
|
- tip
|
||||||
|
services: # docker环境
|
||||||
services: # docker环境
|
|
||||||
- docker
|
- docker
|
||||||
|
|
||||||
branches: # 限定项目分支
|
branches: # 限定项目分支
|
||||||
only:
|
only:
|
||||||
- /^v[0-9](\.[0-9]){2}(-rc[1-9])?$/
|
- /^v[0-9](\.[0-9]){2}(-rc[1-9])?$/
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- curl https://glide.sh/get | sh # 安装glide包管理
|
- curl https://glide.sh/get | sh # 安装glide包管理
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- glide up
|
- glide up
|
||||||
- GOOS=linux GOARCH=amd64 go build # 编译版本
|
- GOOS=linux GOARCH=amd64 go build # 编译版本
|
||||||
- docker build -t registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog . # 构建镜像
|
- docker build -t registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog . # 构建镜像
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
# - if [ "$TRAVIS_BRANCH" =~ ^v[0-9](\.[0-9])+.*$ ]; then
|
- docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" registry.cn-hangzhou.aliyuncs.com
|
||||||
# docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" registry.cn-hangzhou.aliyuncs.com;
|
|
||||||
# docker push registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog;
|
|
||||||
# fi
|
|
||||||
- docker push registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog
|
- docker push registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog
|
||||||
|
- docker tag registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog:$TRAVIS_TAG
|
||||||
|
- docker push registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog:$TRAVIS_TAG
|
||||||
before_deploy:
|
before_deploy:
|
||||||
- ./dist.sh
|
- ./dist.sh
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
provider: releases
|
provider: releases
|
||||||
api_key:
|
api_key:
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# Eiblog Changelog
|
# Eiblog Changelog
|
||||||
|
|
||||||
|
## v1.3.0 (2017-07-13)
|
||||||
|
* 更改 app.yml 配置项,将大部分配置归在 general 常规配置下。注意,部署时请先更新 app.yml。
|
||||||
|
* 静态文件采用动态渲染,即用户不再需要管理 view、static 目录。
|
||||||
|
* 通过 acme.sh 使用双证书啦,可到 Makefile 查看相关信息。
|
||||||
|
* 使用 autocert 自动生成证书功能,从此再也不用担心证书过期,移步 [证书更新](https://github.com/eiblog/eiblog/blob/master/docs/autocert.md)。
|
||||||
|
* 开启配置项 enablehttps, 将自动重定向 http 到 https 啦。
|
||||||
|
* disqus.js 文件由配置指定,请看 app.yml 下的 disqus 相关配置。
|
||||||
|
|
||||||
## v1.2.0 (2017-06-14)
|
## v1.2.0 (2017-06-14)
|
||||||
* 更新评论功能,基础评论 0 回复也可评论了。
|
* 更新评论功能,基础评论 0 回复也可评论了。
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
FROM alpine
|
FROM alpine
|
||||||
MAINTAINER deepzz <deepzz.qi@gmail.com>
|
MAINTAINER deepzz <deepzz.qi@gmail.com>
|
||||||
|
|
||||||
RUN apk update
|
RUN apk add --update --no-cache ca-certificates
|
||||||
RUN apk add ca-certificates
|
|
||||||
ADD static/tzdata/Shanghai /etc/localtime
|
ADD static/tzdata/Shanghai /etc/localtime
|
||||||
|
|
||||||
COPY . /eiblog
|
COPY . /eiblog
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
WORKDIR /eiblog
|
WORKDIR /eiblog
|
||||||
ENTRYPOINT ["./eiblog"]
|
CMD ["sh","-c","/eiblog/eiblog"]
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2014 Manuel Martínez-Almeida
|
Copyright (c) 2017 deepzz deepzz.qi@gmail.com
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
69
Makefile
Normal file
69
Makefile
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
.PHONY: test build deploy dist gencert dhparams ssticket makedir clean
|
||||||
|
# use aliyun dns api to auto renew cert.
|
||||||
|
# env:
|
||||||
|
# export Ali_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
|
||||||
|
# export Ali_Secret="jlsdflanljkljlfdsaklkjflsa"
|
||||||
|
|
||||||
|
docker_registry?=registry.cn-hangzhou.aliyuncs.com
|
||||||
|
acme?=~/.acme.sh
|
||||||
|
acme.sh?=$(acme)/acme.sh
|
||||||
|
config?=/data/eiblog/conf
|
||||||
|
|
||||||
|
|
||||||
|
test:
|
||||||
|
|
||||||
|
build:
|
||||||
|
@echo "go build..."
|
||||||
|
@CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build && \
|
||||||
|
docker build -t $(docker_registry)/deepzz/eiblog:latest .
|
||||||
|
|
||||||
|
deploy:build
|
||||||
|
@docker push $(docker_registry)/deepzz/eiblog:latest
|
||||||
|
|
||||||
|
dist:
|
||||||
|
@./dist.sh
|
||||||
|
|
||||||
|
gencert:makedir
|
||||||
|
@if [ ! -n "$(sans)" ]; then \
|
||||||
|
printf "Need one argument [sans=params]\n"; \
|
||||||
|
printf "example: sans=\"-d domain -d domain\"\n"; \
|
||||||
|
exit 1; \
|
||||||
|
fi; \
|
||||||
|
if [ ! -n "$(cn)" ]; then \
|
||||||
|
printf "Need one argument [cn=params]\n"; \
|
||||||
|
printf "example: cn=domain\n"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
@if [ ! -f $(acme.sh) ]; then \
|
||||||
|
curl https://get.acme.sh | sh; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
@echo "generate rsa cert..."
|
||||||
|
@$(acme.sh) --force --issue --dns dns_ali $(sans) --log \
|
||||||
|
--renew-hook "ct-submit ctlog.api.venafi.com < $(config)/ssl/domain.rsa.pem > $(config)/scts/rsa/venafi.sct \
|
||||||
|
&& ct-submit ctlog.wosign.com < $(config)/ssl/domain.rsa.pem > $(config)/scts/rsa/wosign.sct"
|
||||||
|
@$(acme.sh) --install-cert -d $(cn) \
|
||||||
|
--key-file $(config)/ssl/domain.rsa.key \
|
||||||
|
--fullchain-file $(config)/ssl/domain.rsa.pem \
|
||||||
|
--reloadcmd "service nginx force-reload"
|
||||||
|
|
||||||
|
@echo "generate ecc cert..."
|
||||||
|
@$(acme.sh) --force --issue --dns dns_ali $(sans) -k ec-256 --log \
|
||||||
|
--renew-hook "ct-submit ctlog.api.venafi.com < $(config)/ssl/domain.ecc.pem > $(config)/scts/ecc/venafi.sct \
|
||||||
|
&& ct-submit ctlog.wosign.com < $(config)/ssl/domain.ecc.pem > $(config)/scts/ecc/wosign.sct"
|
||||||
|
@$(acme.sh) --install-cert -d $(cn) --ecc \
|
||||||
|
--key-file $(config)/ssl/domain.ecc.key \
|
||||||
|
--fullchain-file $(config)/ssl/domain.ecc.pem \
|
||||||
|
--reloadcmd "service nginx force-reload"
|
||||||
|
|
||||||
|
dhparams:
|
||||||
|
@openssl dhparam -out $(config)/ssl/dhparams.pem 2048
|
||||||
|
|
||||||
|
ssticket:
|
||||||
|
@openssl rand 48 > $(config)/ssl/session_ticket.key
|
||||||
|
|
||||||
|
makedir:
|
||||||
|
@mkdir -p $(config)/ssl $(config)/scts/rsa $(config)/scts/ecc
|
||||||
|
|
||||||
|
clean:
|
||||||
|
|
||||||
51
README.md
51
README.md
@@ -1,8 +1,8 @@
|
|||||||
# EiBlog [](https://travis-ci.org/eiblog/eiblog) [](LICENSE.md) [](https://github.com/eiblog/eiblog/releases)
|
# EiBlog [](https://travis-ci.org/eiblog/eiblog) [](LICENSE.md) [](https://github.com/eiblog/eiblog/releases)
|
||||||
|
|
||||||
> 系统根据[https://imququ.com](https://imququ.com)一系列文章和方向进行搭建,期间获得了QuQu的很大帮助,在此表示感谢。
|
> 系统根据[https://imququ.com](https://imququ.com)一系列文章和方向进行搭建,期间获得了QuQu的很大帮助,在此表示感谢。
|
||||||
|
|
||||||
用过其它博客系统,不喜欢,不够轻,不够快!自己做过共两款博客系统,完美主义的我(毕竟处女座)也实在是不想再在这件事情上过多纠结了。`Eiblog`应该是一个比较稳定的博客系统,且是博主以后使用的博客系统,稳定性和维护你是不用担心的,唯独该系统部署过程太过复杂,并且不推荐没有计算机知识的朋友搭建,欢迎咨询。该博客的个中优点(明显快,安全),等你体验。
|
用过其它博客系统,不喜欢,不够轻,不够快!自己做过共两款博客系统,完美主义的我(毕竟处女座)也实在是不想再在这件事情上过多纠结了。`Eiblog` 应该是一个比较稳定的博客系统,且是博主以后使用的博客系统,稳定性和维护你是不用担心的,唯独该系统部署过程太过复杂,并且不推荐没有计算机知识的朋友搭建,欢迎咨询。该博客的个中优点(明显快,安全),等你体验。
|
||||||
|
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
||||||
@@ -10,16 +10,19 @@
|
|||||||
|
|
||||||
整个博客系统涉及到模块如下:
|
整个博客系统涉及到模块如下:
|
||||||
|
|
||||||
|
* 自动更新证书:
|
||||||
|
* 接入 [acme/autocert](https://github.com/golang/crypto/tree/master/acme/autocert),在 TLS 层开启全自动更新证书,从此证书的更新再也不用惦记了,不过 Go 的 HTTPS 兼容性不够好(不想兼容),在如部分 IE 和 UC 之类的浏览器不能访问,请悉知。
|
||||||
|
* 如果你采用如 Nginx 代理,推荐使用 [acme.sh](https://github.com/Neilpang/acme.sh) 实现证书的自动部署。博主实现 aliyun dns 的自动验证方式,详见 [Makefile/gencert](https://github.com/eiblog/eiblog/blob/master/Makefile)。
|
||||||
* `MongoDB`,博客采用 mongodb 作为存储数据库。
|
* `MongoDB`,博客采用 mongodb 作为存储数据库。
|
||||||
* `Elasticsearch`,采用`elasticsearch`作为博客的站内搜索,尽管占用内存稍高。
|
* `Elasticsearch`,采用 `elasticsearch` 作为博客的站内搜索,尽管占用内存稍高。
|
||||||
* `Disqus`,作为博客评论系统,国内大部分被墙,故实现两种评论方式。
|
* `Disqus`,作为博客评论系统,国内大部分被墙,故实现两种评论方式。
|
||||||
* `Nginx`,作为反向代理服务器,并做相关`http header`和证书的设置。
|
* `Nginx`,作为反向代理服务器,并做相关 `http header` 和证书的设置。
|
||||||
* `Google Analytics`,作为博客系统的数据分析统计工具。
|
* `Google Analytics`,作为博客系统的数据分析统计工具。
|
||||||
* `七牛 CDN`,作为博客系统的静态文件存储,博文的图片附件什么上传至这里。
|
* `七牛 CDN`,作为博客系统的静态文件存储,博文的图片附件什么上传至这里。
|
||||||
|
|
||||||
### 图片展示
|
### 图片展示
|
||||||
|
|
||||||
可以容易的看到[httpsecurityreport](https://httpsecurityreport.com/?report=deepzz.com)评分`96`,[ssllabs](https://www.ssllabs.com/ssltest/analyze.html?d=deepzz.com&latest)评分`A+`,堪称完美。这些安全的相关配置会在后面的部署过程中接触到。
|
可以容易的看到 [httpsecurityreport](https://httpsecurityreport.com/?report=deepzz.com) 评分`96`,[ssllabs](https://www.ssllabs.com/ssltest/analyze.html?d=deepzz.com&latest) 评分`A+`,[myssl](https://myssl.com/deepzz.com) 评分`A+`,堪称完美。这些安全的相关配置会在后面的部署过程中接触到。
|
||||||
|
|
||||||
相关图片展示:
|
相关图片展示:
|
||||||

|

|
||||||
@@ -33,13 +36,13 @@
|
|||||||
> `注`:图片1,图片2是博客界面,图片3是后台界面,图片4是性能展示。
|
> `注`:图片1,图片2是博客界面,图片3是后台界面,图片4是性能展示。
|
||||||
|
|
||||||
### 极速体验
|
### 极速体验
|
||||||
1. 到[这里](https://github.com/eiblog/eiblog/releases)下载对应平台`.tar.gz`文件。
|
1. 到 [这里](https://github.com/eiblog/eiblog/releases) 下载对应平台 `.tar.gz` 文件。
|
||||||
|
|
||||||
2. 搭建`MongoDB`(必须)和`Elasticsearch`(可选)服务。
|
2. 搭建 `MongoDB`(必须)和 `Elasticsearch`(可选)服务,正式部署需要。
|
||||||
|
|
||||||
3. 修改`/etc/hosts`文件,添加`MongoDB`数据库 IP 地址,如:`127.0.0.1 mongodb`。
|
3. 修改 `/etc/hosts` 文件,添加 `MongoDB` 数据库 IP 地址,如:`127.0.0.1 mongodb`。
|
||||||
|
|
||||||
4. 执行`./eiblog`,运行博客系统。看到:
|
4. 执行 `./eiblog`,运行博客系统。看到:
|
||||||
```
|
```
|
||||||
...
|
...
|
||||||
...
|
...
|
||||||
@@ -47,39 +50,41 @@
|
|||||||
```
|
```
|
||||||
代表运行成功了。
|
代表运行成功了。
|
||||||
|
|
||||||
默认监听`9000`端口,后台`/admin/login`,默认账号密码均为`deepzz`。更多详细请查阅[安装部署](https://github.com/eiblog/eiblog/blob/master/docs/install.md)文档。
|
默认监听 `HTTP 9000` 端口,后台 `/admin/login`,默认账号密码均为 `deepzz`。更多详细请查阅 [安装部署](https://github.com/eiblog/eiblog/blob/master/docs/install.md) 文档。
|
||||||
|
|
||||||
### 特色功能
|
### 特色功能
|
||||||
|
|
||||||
作为博主之心血之作,`Eiblog`实现了什么功能,有什么特点,做了什么优化呢?
|
作为博主之心血之作,`Eiblog` 实现了什么功能,有什么特点,做了什么优化呢?
|
||||||
|
|
||||||
1. 系统目前只有`首页`、`专题`、`归档`、`友链`、`关于`、`搜索`界面。相信已经可以满足大部分用户的需求。
|
1. 系统目前只有 `首页`、`专题`、`归档`、`友链`、`关于`、`搜索` 界面。相信已经可以满足大部分用户的需求。
|
||||||
2. `.js`、`.css`等静态文件本地存储,小图片 base64 内置到 css 中,不会产生网络所带来的延迟,加速网页访问。版本控制方式,动态更新静态文件。
|
2. `.js`、`.css` 等静态文件本地存储,小图片 base64 内置到 css 中,不会产生网络所带来的延迟,加速网页访问。版本控制方式,动态更新静态文件。
|
||||||
3. 采用谷歌统计,并实现异步(将访问信息发给后端,后端提交给谷歌)统计,加速访问速度。
|
3. 采用谷歌统计,并实现异步(将访问信息发给后端,后端提交给谷歌)统计,加速访问速度。
|
||||||
4. 采用直接缓存 markdown 转过的 html 文档的方式,加速后端处理。响应速度均在 3ms 以内,真正极速。
|
4. 采用直接缓存 markdown 转过的 html 文档的方式,加速后端处理。响应速度均在 3ms 以内,真正极速。
|
||||||
5. 通过 Nginx 的配置,开启压缩缩小传输量,服务器传输证书链、开启`Session Resumption`、`Session Ticket`、`OCSP Stapling`等加速证书握手,再次提高速度。
|
5. 通过 Nginx 的配置,开启压缩缩小传输量,服务器传输证书链、开启 `Session Resumption`、`Session Ticket`、`OCSP Stapling `等加速证书握手,再次提高速度。
|
||||||
* `CDN`,使用七牛融合CDN,并`https`化,实现全站`https`。七牛可申请免费证书了。
|
* `CDN`,使用七牛融合CDN,并 `https` 化,实现全站 `https`。七牛可申请免费证书了。
|
||||||
* `CT`,证书透明度检测,提供一个开放的审计和监控系统。可以让任何域名所有者或者 CA 确定证书是否被错误签发或者被恶意使用,从而提高 HTTPS 网站的安全性。
|
* `CT`,证书透明度检测,提供一个开放的审计和监控系统。可以让任何域名所有者或者 CA 确定证书是否被错误签发或者被恶意使用,从而提高 HTTPS 网站的安全性。
|
||||||
* `OSCP`,在线证书状态协议。用来检验证书合法性的在线查询服务.
|
* `OSCP`,在线证书状态协议。用来检验证书合法性的在线查询服务.
|
||||||
* `HSTS`,强制客户端(如浏览器)使用 HTTPS 与服务器创建连接。可以很好的解决 HTTPS 降级攻击。
|
* `HSTS`,强制客户端(如浏览器)使用 HTTPS 与服务器创建连接。可以很好的解决 HTTPS 降级攻击。
|
||||||
* `HPKP`,HTTP公钥固定扩展,防范由「伪造或不正当手段获得网站证书」造成的中间人攻击。该功能让我们选择信任哪些`CA`。
|
* `HPKP`,HTTP 公钥固定扩展,防范由「伪造或不正当手段获得网站证书」造成的中间人攻击。该功能让我们选择信任哪些`CA`。请不要轻易尝试 Nginx 线上运行,因为该配置目前只指定了 Letsencrypt X3 和 TrustAsia G5 证书 pin-sha256。
|
||||||
* `SSL Protocols`,罗列支持的`TLS`协议,SSLv3被证实是不安全的。
|
* `SSL Protocols`,罗列支持的 `TLS` 协议,SSLv3 被证实是不安全的。
|
||||||
* `SSL dhparam`,迪菲赫尔曼密钥交换。
|
* `SSL dhparam`,迪菲赫尔曼密钥交换。
|
||||||
* `Cipher suite`,罗列服务器支持加密套件。
|
* `Cipher suite`,罗列服务器支持加密套件。
|
||||||
6. 文章评论数量(不重要)后端跑定时脚本,定时更新,所以有时评论数是不对的。这样减少了 api 调用,又再次达到加速访问的目的。
|
6. 文章评论数量(不重要)后端跑定时脚本,定时更新,所以有时评论数是不对的。这样减少了 api 调用,又再次达到加速访问的目的。
|
||||||
7. 针对`disqus`被墙原因,实现[Jerry Qu](https://imququ.com)的另类评论方式,保证评论的流畅。
|
7. 针对 `disqus` 被墙原因,实现 [Jerry Qu](https://imququ.com) 的另类评论方式,保证评论的流畅。
|
||||||
8. 开源`Typecho`完整后台系统,全功能`markdown`编辑器,让你体验什么是简洁清爽。
|
8. 开源 `Typecho` 完整后台系统,全功能 `markdown` 编辑器,让你体验什么是简洁清爽。
|
||||||
9. 博客后台直接对接`七牛 SDK`,实现后台上传文件和删除文件的简单功能。
|
9. 博客后台直接对接 `七牛 SDK`,实现后台上传文件和删除文件的简单功能。
|
||||||
10. 采用`elasticsearch`作为站内搜索,添加`google opensearch`功能,搜索更加自然。
|
10. 采用 `elasticsearch` 作为站内搜索,添加 `google opensearch` 功能,搜索更加自然。
|
||||||
|
|
||||||
### 文档
|
### 文档
|
||||||
|
|
||||||
|
* [证书更新](https://github.com/eiblog/eiblog/blob/master/docs/autocert.md)
|
||||||
* [安装部署](https://github.com/eiblog/eiblog/blob/master/docs/install.md)
|
* [安装部署](https://github.com/eiblog/eiblog/blob/master/docs/install.md)
|
||||||
* [写作需知](https://github.com/eiblog/eiblog/blob/master/docs/writing.md)
|
* [写作需知](https://github.com/eiblog/eiblog/blob/master/docs/writing.md)
|
||||||
* [好玩的功能](https://github.com/eiblog/eiblog/blob/master/docs/amusing.md)
|
* [好玩的功能](https://github.com/eiblog/eiblog/blob/master/docs/amusing.md)
|
||||||
|
|
||||||
### 成功搭建者博客
|
### 成功搭建者博客
|
||||||
|
|
||||||
* [https://razeencheng.com/](https://razeencheng.com/) - Razeen's Blog
|
* [https://razeen.me](https://razeen.me) - Razeen's Blog
|
||||||
|
* [https://mxthd.me](https://mxthd.me) - 梦醒逃荒岛
|
||||||
|
|
||||||
如果你的博客使用`Eiblog`搭建,你可以在[这里](https://github.com/eiblog/eiblog/issues/1)提交网址。
|
如果你的博客使用`Eiblog`搭建,你可以在 [这里](https://github.com/eiblog/eiblog/issues/1) 提交网址。
|
||||||
|
|||||||
25
api.go
25
api.go
@@ -11,16 +11,20 @@ import (
|
|||||||
|
|
||||||
"github.com/eiblog/eiblog/setting"
|
"github.com/eiblog/eiblog/setting"
|
||||||
"github.com/eiblog/utils/logd"
|
"github.com/eiblog/utils/logd"
|
||||||
|
"github.com/eiblog/utils/mgo"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"gopkg.in/mgo.v2/bson"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// 成功
|
||||||
NOTICE_SUCCESS = "success"
|
NOTICE_SUCCESS = "success"
|
||||||
NOTICE_NOTICE = "notice"
|
// 注意
|
||||||
NOTICE_ERROR = "error"
|
NOTICE_NOTICE = "notice"
|
||||||
|
// 错误
|
||||||
|
NOTICE_ERROR = "error"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 全局 API
|
||||||
var APIs = make(map[string]func(c *gin.Context))
|
var APIs = make(map[string]func(c *gin.Context))
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -61,7 +65,8 @@ func apiAccount(c *gin.Context) {
|
|||||||
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
|
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err := UpdateAccountField(bson.M{"$set": bson.M{"email": e, "phonen": pn, "address": ad}})
|
|
||||||
|
err := UpdateAccountField(mgo.M{"$set": mgo.M{"email": e, "phonen": pn, "address": ad}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||||
return
|
return
|
||||||
@@ -83,7 +88,8 @@ func apiBlog(c *gin.Context) {
|
|||||||
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
|
responseNotice(c, NOTICE_NOTICE, "参数错误", "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err := UpdateAccountField(bson.M{"$set": bson.M{"blogger.blogname": bn, "blogger.btitle": bt, "blogger.beian": ba, "blogger.subtitle": st, "blogger.seriessay": ss, "blogger.archivessay": as}})
|
|
||||||
|
err := UpdateAccountField(mgo.M{"$set": mgo.M{"blogger.blogname": bn, "blogger.btitle": bt, "blogger.beian": ba, "blogger.subtitle": st, "blogger.seriessay": ss, "blogger.archivessay": as}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||||
return
|
return
|
||||||
@@ -117,7 +123,8 @@ func apiPassword(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
newPwd := EncryptPasswd(Ei.Username, nw)
|
newPwd := EncryptPasswd(Ei.Username, nw)
|
||||||
err := UpdateAccountField(bson.M{"$set": bson.M{"password": newPwd}})
|
|
||||||
|
err := UpdateAccountField(mgo.M{"$set": mgo.M{"password": newPwd}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
responseNotice(c, NOTICE_NOTICE, err.Error(), "")
|
||||||
return
|
return
|
||||||
@@ -136,6 +143,7 @@ func apiPostDelete(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
responseNotice(c, NOTICE_SUCCESS, "删除成功", "")
|
responseNotice(c, NOTICE_SUCCESS, "删除成功", "")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err = c.Request.ParseForm()
|
err = c.Request.ParseForm()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -187,6 +195,7 @@ func apiPostAdd(c *gin.Context) {
|
|||||||
c.Redirect(http.StatusFound, "/admin/manage-posts")
|
c.Redirect(http.StatusFound, "/admin/manage-posts")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
do = c.PostForm("do") // auto or save or publish
|
do = c.PostForm("do") // auto or save or publish
|
||||||
slug := c.PostForm("slug")
|
slug := c.PostForm("slug")
|
||||||
title := c.PostForm("title")
|
title := c.PostForm("title")
|
||||||
@@ -245,7 +254,7 @@ func apiPostAdd(c *gin.Context) {
|
|||||||
if CheckBool(update) {
|
if CheckBool(update) {
|
||||||
artc.UpdateTime = time.Now()
|
artc.UpdateTime = time.Now()
|
||||||
}
|
}
|
||||||
err = UpdateArticle(bson.M{"id": artc.ID}, artc)
|
err = UpdateArticle(mgo.M{"id": artc.ID}, artc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logd.Error(err)
|
logd.Error(err)
|
||||||
return
|
return
|
||||||
@@ -258,7 +267,7 @@ func apiPostAdd(c *gin.Context) {
|
|||||||
// elasticsearch 索引
|
// elasticsearch 索引
|
||||||
ElasticIndex(artc)
|
ElasticIndex(artc)
|
||||||
DoPings(slug)
|
DoPings(slug)
|
||||||
if artc.ID >= setting.Conf.StartID {
|
if artc.ID >= setting.Conf.General.StartID {
|
||||||
ManageTagsArticle(artc, true, ADD)
|
ManageTagsArticle(artc, true, ADD)
|
||||||
ManageSeriesArticle(artc, true, ADD)
|
ManageSeriesArticle(artc, true, ADD)
|
||||||
ManageArchivesArticle(artc, true, ADD)
|
ManageArchivesArticle(artc, true, ADD)
|
||||||
|
|||||||
20
back.go
20
back.go
@@ -3,6 +3,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -11,11 +12,12 @@ import (
|
|||||||
|
|
||||||
"github.com/eiblog/eiblog/setting"
|
"github.com/eiblog/eiblog/setting"
|
||||||
"github.com/eiblog/utils/logd"
|
"github.com/eiblog/utils/logd"
|
||||||
|
"github.com/eiblog/utils/mgo"
|
||||||
"github.com/gin-gonic/contrib/sessions"
|
"github.com/gin-gonic/contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"gopkg.in/mgo.v2/bson"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 是否登录
|
||||||
func isLogin(c *gin.Context) bool {
|
func isLogin(c *gin.Context) bool {
|
||||||
session := sessions.Default(c)
|
session := sessions.Default(c)
|
||||||
v := session.Get("username")
|
v := session.Get("username")
|
||||||
@@ -25,6 +27,7 @@ func isLogin(c *gin.Context) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 登陆过滤
|
||||||
func AuthFilter() gin.HandlerFunc {
|
func AuthFilter() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
if !isLogin(c) {
|
if !isLogin(c) {
|
||||||
@@ -51,6 +54,7 @@ func HandleLogin(c *gin.Context) {
|
|||||||
RenderHTMLBack(c, "login.html", gin.H{"BTitle": Ei.BTitle})
|
RenderHTMLBack(c, "login.html", gin.H{"BTitle": Ei.BTitle})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 登陆接口
|
||||||
func HandleLoginPost(c *gin.Context) {
|
func HandleLoginPost(c *gin.Context) {
|
||||||
user := c.PostForm("user")
|
user := c.PostForm("user")
|
||||||
pwd := c.PostForm("password")
|
pwd := c.PostForm("password")
|
||||||
@@ -61,7 +65,7 @@ func HandleLoginPost(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if Ei.Username != user || !VerifyPasswd(Ei.Password, user, pwd) {
|
if Ei.Username != user || !VerifyPasswd(Ei.Password, user, pwd) {
|
||||||
logd.Printf("账号或密码错误 %s, %s", user, pwd)
|
logd.Printf("账号或密码错误 %s, %s\n", user, pwd)
|
||||||
c.Redirect(http.StatusFound, "/admin/login")
|
c.Redirect(http.StatusFound, "/admin/login")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -70,12 +74,12 @@ func HandleLoginPost(c *gin.Context) {
|
|||||||
session.Save()
|
session.Save()
|
||||||
Ei.LoginIP = c.ClientIP()
|
Ei.LoginIP = c.ClientIP()
|
||||||
Ei.LoginTime = time.Now()
|
Ei.LoginTime = time.Now()
|
||||||
UpdateAccountField(bson.M{"$set": bson.M{"loginip": Ei.LoginIP, "logintime": Ei.LoginTime}})
|
UpdateAccountField(mgo.M{"$set": mgo.M{"loginip": Ei.LoginIP, "logintime": Ei.LoginTime}})
|
||||||
c.Redirect(http.StatusFound, "/admin/profile")
|
c.Redirect(http.StatusFound, "/admin/profile")
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetBack() gin.H {
|
func GetBack() gin.H {
|
||||||
return gin.H{"Author": Ei.Username, "Kodo": setting.Conf.Kodo}
|
return gin.H{"Author": Ei.Username, "Qiniu": setting.Conf.Qiniu}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 个人配置
|
// 个人配置
|
||||||
@@ -115,11 +119,13 @@ func HandlePost(c *gin.Context) {
|
|||||||
for tag, _ := range Ei.Tags {
|
for tag, _ := range Ei.Tags {
|
||||||
tags = append(tags, T{tag, tag})
|
tags = append(tags, T{tag, tag})
|
||||||
}
|
}
|
||||||
h["Tags"] = tags
|
str, _ := json.Marshal(tags)
|
||||||
|
h["Tags"] = string(str)
|
||||||
c.Status(http.StatusOK)
|
c.Status(http.StatusOK)
|
||||||
RenderHTMLBack(c, "admin-post", h)
|
RenderHTMLBack(c, "admin-post", h)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 删除草稿
|
||||||
func HandleDraftDelete(c *gin.Context) {
|
func HandleDraftDelete(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Query("cid"))
|
id, err := strconv.Atoi(c.Query("cid"))
|
||||||
if err != nil || id < 1 {
|
if err != nil || id < 1 {
|
||||||
@@ -154,7 +160,7 @@ func HandlePosts(c *gin.Context) {
|
|||||||
h["Serie"] = se
|
h["Serie"] = se
|
||||||
h["KW"] = kw
|
h["KW"] = kw
|
||||||
var max int
|
var max int
|
||||||
max, h["List"] = PageListBack(se, kw, false, false, pg, setting.Conf.PageSize)
|
max, h["List"] = PageListBack(se, kw, false, false, pg, setting.Conf.General.PageSize)
|
||||||
if pg < max {
|
if pg < max {
|
||||||
vals.Set("page", fmt.Sprint(pg+1))
|
vals.Set("page", fmt.Sprint(pg+1))
|
||||||
h["Next"] = vals.Encode()
|
h["Next"] = vals.Encode()
|
||||||
@@ -184,6 +190,7 @@ func HandleSeries(c *gin.Context) {
|
|||||||
RenderHTMLBack(c, "admin-series", h)
|
RenderHTMLBack(c, "admin-series", h)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 编辑专题
|
||||||
func HandleSerie(c *gin.Context) {
|
func HandleSerie(c *gin.Context) {
|
||||||
h := GetBack()
|
h := GetBack()
|
||||||
id, err := strconv.Atoi(c.Query("mid"))
|
id, err := strconv.Atoi(c.Query("mid"))
|
||||||
@@ -276,6 +283,7 @@ func HandleAPI(c *gin.Context) {
|
|||||||
api(c)
|
api(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 渲染 html
|
||||||
func RenderHTMLBack(c *gin.Context, name string, data gin.H) {
|
func RenderHTMLBack(c *gin.Context, name string, data gin.H) {
|
||||||
if name == "login.html" {
|
if name == "login.html" {
|
||||||
err := Tmpl.ExecuteTemplate(c.Writer, name, data)
|
err := Tmpl.ExecuteTemplate(c.Writer, name, data)
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
echo "go build..."
|
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build && \
|
|
||||||
|
|
||||||
domain="registry.cn-hangzhou.aliyuncs.com" && \
|
|
||||||
docker build -t $domain/deepzz/eiblog . && \
|
|
||||||
read -p "是否上传到服务器(y/n):" word && \
|
|
||||||
if [ $word = "y" ] ;then
|
|
||||||
docker push $domain/deepzz/eiblog
|
|
||||||
fi
|
|
||||||
7
check.go
7
check.go
@@ -6,25 +6,30 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 检查 email
|
||||||
func CheckEmail(e string) bool {
|
func CheckEmail(e string) bool {
|
||||||
reg := regexp.MustCompile(`^(\w)+([\.\-]\w+)*@(\w)+((\.\w+)+)$`)
|
reg := regexp.MustCompile(`^(\w)+([\.\-]\w+)*@(\w)+((\.\w+)+)$`)
|
||||||
return reg.MatchString(e)
|
return reg.MatchString(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查 domain
|
||||||
func CheckDomain(domain string) bool {
|
func CheckDomain(domain string) bool {
|
||||||
reg := regexp.MustCompile(`^(http://|https://)?[0-9a-zA-Z]+[0-9a-zA-Z\.-]*\.[a-zA-Z]{2,4}$`)
|
reg := regexp.MustCompile(`^(http://|https://)?[0-9a-zA-Z]+[0-9a-zA-Z\.-]*\.[a-zA-Z]{2,4}$`)
|
||||||
return reg.MatchString(domain)
|
return reg.MatchString(domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查 sms
|
||||||
func CheckSMS(sms string) bool {
|
func CheckSMS(sms string) bool {
|
||||||
reg := regexp.MustCompile(`^\+\d+$`)
|
reg := regexp.MustCompile(`^\+\d+$`)
|
||||||
return reg.MatchString(sms)
|
return reg.MatchString(sms)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查 password
|
||||||
func CheckPwd(pwd string) bool {
|
func CheckPwd(pwd string) bool {
|
||||||
return len(pwd) > 5 && len(pwd) < 19
|
return len(pwd) > 5 && len(pwd) < 19
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查日期
|
||||||
func CheckDate(date string) time.Time {
|
func CheckDate(date string) time.Time {
|
||||||
if t, err := time.ParseInLocation("2006-01-02 15:04", date, time.Local); err == nil {
|
if t, err := time.ParseInLocation("2006-01-02 15:04", date, time.Local); err == nil {
|
||||||
return t
|
return t
|
||||||
@@ -32,6 +37,7 @@ func CheckDate(date string) time.Time {
|
|||||||
return time.Now()
|
return time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查 id
|
||||||
func CheckSerieID(sid string) int32 {
|
func CheckSerieID(sid string) int32 {
|
||||||
if id, err := strconv.Atoi(sid); err == nil {
|
if id, err := strconv.Atoi(sid); err == nil {
|
||||||
return int32(id)
|
return int32(id)
|
||||||
@@ -39,6 +45,7 @@ func CheckSerieID(sid string) int32 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bool
|
||||||
func CheckBool(str string) bool {
|
func CheckBool(str string) bool {
|
||||||
return str == "true" || str == "1"
|
return str == "true" || str == "1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,28 +2,40 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCheckEmail(t *testing.T) {
|
func TestCheckEmail(t *testing.T) {
|
||||||
e := "xx@email.com"
|
emails := []string{
|
||||||
e1 := "xxxxemail.com"
|
"xx@email.com",
|
||||||
e2 := "xxx#email.com"
|
"xxxxemail.com",
|
||||||
|
"xxx#email.com",
|
||||||
|
}
|
||||||
|
|
||||||
t.Log(CheckEmail(e))
|
for i, v := range emails {
|
||||||
t.Log(CheckEmail(e1))
|
if i == 0 {
|
||||||
t.Log(CheckEmail(e2))
|
assert.True(t, CheckEmail(v))
|
||||||
|
} else {
|
||||||
|
assert.False(t, CheckEmail(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckDomain(t *testing.T) {
|
func TestCheckDomain(t *testing.T) {
|
||||||
d := "123.com"
|
domains := []string{
|
||||||
d1 := "http://123.com"
|
"123.com",
|
||||||
d2 := "https://123.com"
|
"http://123.com",
|
||||||
d3 := "123#.com"
|
"https://123.com",
|
||||||
d4 := "123.coooom"
|
"123#.com",
|
||||||
|
"123.coooom",
|
||||||
|
}
|
||||||
|
|
||||||
t.Log(CheckDomain(d))
|
for i, v := range domains {
|
||||||
t.Log(CheckDomain(d1))
|
if i > 2 {
|
||||||
t.Log(CheckDomain(d2))
|
assert.False(t, CheckDomain(v))
|
||||||
t.Log(CheckDomain(d3))
|
} else {
|
||||||
t.Log(CheckDomain(d4))
|
assert.True(t, CheckDomain(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
86
conf/app.yml
86
conf/app.yml
@@ -1,56 +1,74 @@
|
|||||||
# 运行模式 dev or prod
|
# 运行模式 dev or prod
|
||||||
runmode: dev
|
runmode: dev
|
||||||
# 回收箱保留48小时
|
# 静态文件版本
|
||||||
trash: -48
|
staticversion: 1
|
||||||
# 定时清理回收箱,%d小时
|
# superfeedr url
|
||||||
clean: 1
|
feedrurl: https://deepzz.superfeedr.com/
|
||||||
# 首页展示文章数量
|
# 热搜词配置
|
||||||
pagenum: 10
|
hotwords:
|
||||||
# 管理界面
|
- docker
|
||||||
pagesize: 20
|
- mongodb
|
||||||
# 自动截取预览, 字符数
|
- curl
|
||||||
length: 400
|
- dns
|
||||||
# 截取预览标识
|
# ping rpcs 地址
|
||||||
identifier: <!--more-->
|
pingrpcs:
|
||||||
# 文章描述前缀
|
- http://ping.baidu.com/ping/RPC2
|
||||||
description: "Desc:"
|
- http://blogsearch.google.com/ping/RPC2
|
||||||
# 起始ID,预留id不时之需, 不用管
|
- http://rpc.pingomatic.com/
|
||||||
startid: 11
|
# 常规配置
|
||||||
# elasticsearch url
|
general:
|
||||||
searchurl: http://elasticsearch:9200
|
# 首页展示文章数量
|
||||||
|
pagenum: 10
|
||||||
|
# 管理界面
|
||||||
|
pagesize: 20
|
||||||
|
# 起始ID,预留id不时之需, 不用管
|
||||||
|
startid: 11
|
||||||
|
# 文章描述前缀
|
||||||
|
descprefix: "Desc:"
|
||||||
|
# 截取预览标识
|
||||||
|
identifier: <!--more-->
|
||||||
|
# 自动截取预览, 字符数
|
||||||
|
length: 400
|
||||||
|
# 回收箱保留48小时
|
||||||
|
trash: -48
|
||||||
|
# 定时清理回收箱,每 %d 小时
|
||||||
|
clean: 1
|
||||||
# 评论相关
|
# 评论相关
|
||||||
disqus:
|
disqus:
|
||||||
shortname: deepzz
|
shortname: deepzz
|
||||||
publickey: wdSgxRm9rdGAlLKFcFdToBe3GT4SibmV7Y8EjJQ0r4GWXeKtxpopMAeIeoI2dTEg
|
publickey: wdSgxRm9rdGAlLKFcFdToBe3GT4SibmV7Y8EjJQ0r4GWXeKtxpopMAeIeoI2dTEg
|
||||||
|
accesstoken: 50023908f39f4607957e909b495326af
|
||||||
postscount: https://disqus.com/api/3.0/threads/set.json
|
postscount: https://disqus.com/api/3.0/threads/set.json
|
||||||
postslist: https://disqus.com/api/3.0/threads/listPosts.json
|
postslist: https://disqus.com/api/3.0/threads/listPosts.json
|
||||||
postcreate: https://disqus.com/api/3.0/posts/create.json
|
postcreate: https://disqus.com/api/3.0/posts/create.json
|
||||||
|
postapprove: https://disqus.com/api/3.0/posts/approve.json
|
||||||
|
# disqus.js 文件名
|
||||||
|
embed: disqus_7d3cf2.js
|
||||||
|
# 获取评论数量间隔
|
||||||
interval: 5
|
interval: 5
|
||||||
# 热搜词配置
|
|
||||||
hotwords:
|
|
||||||
- docker
|
|
||||||
# 谷歌统计
|
# 谷歌统计
|
||||||
google:
|
google:
|
||||||
|
url: https://www.google-analytics.com/collect
|
||||||
tid: UA-77251712-1
|
tid: UA-77251712-1
|
||||||
v: "1"
|
v: "1"
|
||||||
t: pageview
|
t: pageview
|
||||||
# 静态文件版本
|
|
||||||
staticversion: 1
|
|
||||||
# 七牛CDN
|
# 七牛CDN
|
||||||
kodo:
|
qiniu:
|
||||||
name: eiblog
|
bucket: eiblog
|
||||||
domain: st.deepzz.com
|
domain: st.deepzz.com
|
||||||
accesskey: MB6AXl_Sj_mmFsL-Lt59Dml2Vmy2o8XMmiCbbSeC
|
accesskey: MB6AXl_Sj_mmFsL-Lt59Dml2Vmy2o8XMmiCbbSeC
|
||||||
secretkey: BIrMy0fsZ0_SHNceNXk3eDuo7WmVYzj2-zrmd5Tf
|
secretkey: BIrMy0fsZ0_SHNceNXk3eDuo7WmVYzj2-zrmd5Tf
|
||||||
# 运行模式
|
# 运行模式
|
||||||
mode:
|
mode:
|
||||||
# 默认开启HTTP
|
# http server
|
||||||
enablehttp: true
|
enablehttp: true
|
||||||
httpport: 9000
|
httpport: 9000
|
||||||
|
# https server
|
||||||
enablehttps: false
|
enablehttps: false
|
||||||
httpsport: 443
|
autocert: false
|
||||||
certfile: conf/certs/domain.pem
|
httpsport: 9001
|
||||||
keyfile: conf/certs/domain.key
|
certfile: conf/ssl/domain.rsa.pem
|
||||||
|
keyfile: conf/ssl/domain.rsa.key
|
||||||
domain: deepzz.com
|
domain: deepzz.com
|
||||||
# twitter地址: twitter.com/chenqijing2
|
# twitter地址: twitter.com/chenqijing2
|
||||||
twitter:
|
twitter:
|
||||||
@@ -58,16 +76,8 @@ twitter:
|
|||||||
site: chenqijing2
|
site: chenqijing2
|
||||||
image: st.deepzz.com/static/img/avatar.jpg
|
image: st.deepzz.com/static/img/avatar.jpg
|
||||||
address: twitter.com/chenqijing2
|
address: twitter.com/chenqijing2
|
||||||
# superfeedr url
|
|
||||||
feedrurl: https://deepzz.superfeedr.com/
|
|
||||||
# ping rpcs 地址
|
|
||||||
pingrpcs:
|
|
||||||
- http://ping.baidu.com/ping/RPC2
|
|
||||||
- http://blogsearch.google.com/ping/RPC2
|
|
||||||
- http://rpc.pingomatic.com/
|
|
||||||
|
|
||||||
# 以下数据项供初始化使用,仅首次运行有效。
|
# 数据初始化操作,可到博客后台修改
|
||||||
# 若需要修改,请到博客后台操作。
|
|
||||||
account:
|
account:
|
||||||
# *后台登录用户名
|
# *后台登录用户名
|
||||||
username: deepzz
|
username: deepzz
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
# like 192.168.99.100:true
|
||||||
|
|||||||
0
conf/es/config/scripts/empty.groovy
Normal file
0
conf/es/config/scripts/empty.groovy
Normal file
@@ -1,157 +0,0 @@
|
|||||||
server {
|
|
||||||
listen 443 ssl http2 fastopen=3 reuseport;
|
|
||||||
|
|
||||||
server_name www.deepzz.com deepzz.com;
|
|
||||||
server_tokens off;
|
|
||||||
|
|
||||||
include /data/eiblog/conf/nginx/ip.blacklist;
|
|
||||||
|
|
||||||
# 现在一般证书是内置的。可以注释该项
|
|
||||||
# https://imququ.com/post/certificate-transparency.html#toc-2
|
|
||||||
# ssl_ct on;
|
|
||||||
# ssl_ct_static_scts /data/eiblog/conf/scts;
|
|
||||||
|
|
||||||
# 中间证书 + 站点证书
|
|
||||||
ssl_certificate /data/eiblog/conf/ssl/domain.pem;
|
|
||||||
|
|
||||||
# 创建 CSR 文件时用的密钥
|
|
||||||
ssl_certificate_key /data/eiblog/conf/ssl/domain.key;
|
|
||||||
|
|
||||||
# openssl dhparam -out dhparams.pem 2048
|
|
||||||
# https://weakdh.org/sysadmin.html
|
|
||||||
ssl_dhparam /data/eiblog/conf/ssl/dhparams.pem;
|
|
||||||
|
|
||||||
# https://github.com/cloudflare/sslconfig/blob/master/conf
|
|
||||||
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
|
|
||||||
|
|
||||||
# 如果启用了 RSA + ECDSA 双证书,Cipher Suite 可以参考以下配置:
|
|
||||||
# ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:!MD5;
|
|
||||||
|
|
||||||
ssl_prefer_server_ciphers on;
|
|
||||||
|
|
||||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
|
||||||
|
|
||||||
ssl_session_cache shared:SSL:50m;
|
|
||||||
ssl_session_timeout 1d;
|
|
||||||
|
|
||||||
ssl_session_tickets on;
|
|
||||||
|
|
||||||
# openssl rand 48 > session_ticket.key
|
|
||||||
# 单机部署可以不指定 ssl_session_ticket_key
|
|
||||||
# ssl_session_ticket_key /data/eiblog/conf/ssl/session_ticket.key;
|
|
||||||
|
|
||||||
ssl_stapling on;
|
|
||||||
ssl_stapling_verify on;
|
|
||||||
|
|
||||||
# 根证书 + 中间证书
|
|
||||||
# https://imququ.com/post/why-can-not-turn-on-ocsp-stapling.html
|
|
||||||
ssl_trusted_certificate /data/eiblog/conf/ssl/full_chained.pem;
|
|
||||||
|
|
||||||
resolver 114.114.114.114 8.8.8.8 valid=300s;
|
|
||||||
resolver_timeout 10s;
|
|
||||||
|
|
||||||
access_log /data/eiblog/logdata/nginx.log;
|
|
||||||
|
|
||||||
if ($request_method !~ ^(GET|HEAD|POST|OPTIONS)$ ) {
|
|
||||||
return 444;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($host != 'deepzz.com' ) {
|
|
||||||
rewrite ^/(.*)$ https://deepzz.com/$1 permanent;
|
|
||||||
}
|
|
||||||
|
|
||||||
# webmaster 站点验证相关
|
|
||||||
location ~* (robots\.txt|favicon\.ico|crossdomain\.xml|google4c90d18e696bdcf8\.html|BingSiteAuth\.xml)$ {
|
|
||||||
root /data/eiblog/static;
|
|
||||||
expires 1d;
|
|
||||||
}
|
|
||||||
|
|
||||||
# imququ 的上传文件相关,未用到
|
|
||||||
location ^~ /static/uploads/ {
|
|
||||||
root /home/jerry/www/imququ.com/www;
|
|
||||||
add_header Access-Control-Allow-Origin *;
|
|
||||||
|
|
||||||
set $expires_time max;
|
|
||||||
|
|
||||||
valid_referers blocked none server_names *.qgy18.com *.inoreader.com feedly.com *.feedly.com www.udpwork.com theoldreader.com digg.com *.feiworks.com *.newszeit.com r.mail.qq.com yuedu.163.com *.w3ctech.com;
|
|
||||||
if ($invalid_referer) {
|
|
||||||
set $expires_time -1;
|
|
||||||
return 403;
|
|
||||||
}
|
|
||||||
|
|
||||||
expires $expires_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ^~ /static/ {
|
|
||||||
root /data/eiblog;
|
|
||||||
add_header Access-Control-Allow-Origin *;
|
|
||||||
expires max;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ^~ /admin/ {
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
|
|
||||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
|
|
||||||
|
|
||||||
# deny 将完全不允许页面被嵌套,可能会导致一些异常。如果遇到这样的问题,建议改成 SAMEORIGIN
|
|
||||||
# https://imququ.com/post/web-security-and-response-header.html#toc-1
|
|
||||||
add_header X-Frame-Options deny;
|
|
||||||
|
|
||||||
add_header X-Content-Type-Options nosniff;
|
|
||||||
|
|
||||||
proxy_set_header Connection "";
|
|
||||||
proxy_set_header Host deepzz.com;
|
|
||||||
proxy_set_header X-Real_IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
|
|
||||||
proxy_pass http://127.0.0.1:9000;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
|
|
||||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
|
|
||||||
add_header X-Frame-Options deny;
|
|
||||||
add_header X-Content-Type-Options nosniff;
|
|
||||||
# 改deepzz相关的
|
|
||||||
add_header Content-Security-Policy "default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' blob: https:; img-src data: https: https://st.deepzz.com; style-src 'unsafe-inline' https:; child-src https:; connect-src 'self' https://translate.googleapis.com; frame-src https://disqus.com https://www.slideshare.net";
|
|
||||||
# 中间证书证书指纹
|
|
||||||
# https://imququ.com/post/http-public-key-pinning.html
|
|
||||||
add_header Public-Key-Pins 'pin-sha256="lnsM2T/O9/J84sJFdnrpsFp3awZJ+ZZbYpCWhGloaHI="; pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg="; max-age=2592000; includeSubDomains';
|
|
||||||
add_header Cache-Control no-cache;
|
|
||||||
add_header X-Via Aliyun.QingDao;
|
|
||||||
add_header X-XSS-Protection "1; mode=block";
|
|
||||||
|
|
||||||
proxy_ignore_headers Set-Cookie;
|
|
||||||
|
|
||||||
proxy_hide_header Vary;
|
|
||||||
proxy_hide_header X-Powered-By;
|
|
||||||
|
|
||||||
proxy_set_header Connection "";
|
|
||||||
proxy_set_header Host deepzz.com;
|
|
||||||
proxy_set_header X-Real_IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
|
|
||||||
proxy_pass http://127.0.0.1:9000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
server_name www.deepzz.com deepzz.com;
|
|
||||||
server_tokens off;
|
|
||||||
|
|
||||||
access_log /dev/null;
|
|
||||||
|
|
||||||
if ($request_method !~ ^(GET|HEAD|POST)$ ) {
|
|
||||||
return 444;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ^~ /.well-known/acme-challenge/ {
|
|
||||||
alias /data/letsencrypt/challenges/;
|
|
||||||
try_files $uri =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
rewrite ^/(.*)$ https://deepzz.com/$1 permanent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
134
conf/nginx/domain/eiblog.conf
Normal file
134
conf/nginx/domain/eiblog.conf
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
server {
|
||||||
|
listen 443 ssl http2 fastopen=3 reuseport;
|
||||||
|
|
||||||
|
server_name www.deepzz.com deepzz.com;
|
||||||
|
server_tokens off;
|
||||||
|
|
||||||
|
access_log /data/eiblog/logdata/nginx.log;
|
||||||
|
|
||||||
|
# ip 黑名单
|
||||||
|
include /data/eiblog/conf/nginx/ip.blacklist;
|
||||||
|
|
||||||
|
# 现在一般证书是内置的。letsencrypt 暂未
|
||||||
|
# https://imququ.com/post/certificate-transparency.html#toc-2
|
||||||
|
ssl_ct on;
|
||||||
|
|
||||||
|
# 中间证书 + 根证书
|
||||||
|
# https://imququ.com/post/why-can-not-turn-on-ocsp-stapling.html
|
||||||
|
ssl_trusted_certificate /data/eiblog/conf/ssl/full_chained.pem;
|
||||||
|
|
||||||
|
# 站点证书 + 中间证书,私钥
|
||||||
|
ssl_certificate /data/eiblog/conf/ssl/domain.rsa.pem;
|
||||||
|
ssl_certificate_key /data/eiblog/conf/ssl/domain.rsa.key;
|
||||||
|
ssl_ct_static_scts /data/eiblog/conf/scts/rsa/;
|
||||||
|
# ssl_certificate /data/eiblog/conf/ssl/domain.ecc.pem;
|
||||||
|
# ssl_certificate_key /data/eiblog/conf/ssl/domain.ecc.key;
|
||||||
|
# ssl_ct_static_scts /data/eiblog/conf/scts/ecc/;
|
||||||
|
|
||||||
|
# openssl dhparam -out dhparams.pem 2048
|
||||||
|
# https://weakdh.org/sysadmin.html
|
||||||
|
ssl_dhparam /data/eiblog/conf/ssl/dhparams.pem;
|
||||||
|
|
||||||
|
# openssl rand 48 > session_ticket.key
|
||||||
|
# 单机部署可以不指定 ssl_session_ticket_key
|
||||||
|
# ssl_session_ticket_key /data/eiblog/conf/ssl/session_ticket.key;
|
||||||
|
|
||||||
|
# https://github.com/cloudflare/sslconfig/blob/master/conf
|
||||||
|
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
|
||||||
|
|
||||||
|
# 如果启用了 RSA + ECDSA 双证书,Cipher Suite 可以参考以下配置:
|
||||||
|
# ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:!MD5;
|
||||||
|
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||||
|
ssl_session_cache shared:SSL:50m;
|
||||||
|
ssl_session_timeout 1d;
|
||||||
|
ssl_session_tickets on;
|
||||||
|
|
||||||
|
# ssl stapling
|
||||||
|
ssl_stapling on;
|
||||||
|
ssl_stapling_verify on;
|
||||||
|
resolver 114.114.114.114 8.8.8.8 valid=300s;
|
||||||
|
resolver_timeout 10s;
|
||||||
|
|
||||||
|
if ($request_method !~ ^(GET|HEAD|POST|OPTIONS)$ ) {
|
||||||
|
return 444;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($host != 'deepzz.com' ) {
|
||||||
|
rewrite ^/(.*)$ https://deepzz.com/$1 permanent;
|
||||||
|
}
|
||||||
|
|
||||||
|
# webmaster 站点验证相关
|
||||||
|
location ~* (google4c90d18e696bdcf8\.html|BingSiteAuth\.xml)$ {
|
||||||
|
root /data/eiblog/static;
|
||||||
|
expires 1d;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /admin/ {
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
|
||||||
|
|
||||||
|
# deny 将完全不允许页面被嵌套,可能会导致一些异常。如果遇到这样的问题,建议改成 SAMEORIGIN
|
||||||
|
# https://imququ.com/post/web-security-and-response-header.html#toc-1
|
||||||
|
add_header X-Frame-Options deny;
|
||||||
|
add_header X-Powered-By eiblog/1.3.0;
|
||||||
|
add_header X-Content-Type-Options nosniff;
|
||||||
|
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
proxy_set_header Host deepzz.com;
|
||||||
|
proxy_set_header X-Real_IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
||||||
|
proxy_pass http://127.0.0.1:9000;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
|
||||||
|
add_header X-Frame-Options deny;
|
||||||
|
add_header X-Content-Type-Options nosniff;
|
||||||
|
# 改deepzz相关的
|
||||||
|
add_header Content-Security-Policy "default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' blob: https:; img-src data: https: https://st.deepzz.com; media-src https://st.deepzz.com; style-src 'unsafe-inline' https:; child-src https:; connect-src 'self' https://translate.googleapis.com; frame-src https://disqus.com https://www.slideshare.net";
|
||||||
|
# 中间证书证书指纹
|
||||||
|
# https://imququ.com/post/http-public-key-pinning.html
|
||||||
|
add_header Public-Key-Pins 'pin-sha256="IiSbZ4pMDEyXvtl7Lg8K3FNmJcTAhKUTrB2FQOaAO/s="; pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg="; max-age=2592000;';
|
||||||
|
add_header Cache-Control no-cache;
|
||||||
|
add_header X-Via Aliyun.QingDao;
|
||||||
|
add_header X-XSS-Protection "1; mode=block";
|
||||||
|
add_header X-Powered-By eiblog/1.3.0;
|
||||||
|
|
||||||
|
proxy_ignore_headers Set-Cookie;
|
||||||
|
proxy_hide_header Vary;
|
||||||
|
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
proxy_set_header Host deepzz.com;
|
||||||
|
proxy_set_header X-Real_IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
||||||
|
proxy_pass http://127.0.0.1:9000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
server_name www.deepzz.com deepzz.com;
|
||||||
|
server_tokens off;
|
||||||
|
|
||||||
|
access_log /dev/null;
|
||||||
|
|
||||||
|
if ($request_method !~ ^(GET|HEAD|POST|OPTIONS)$ ) {
|
||||||
|
return 444;
|
||||||
|
}
|
||||||
|
|
||||||
|
# letsencrypt file verify
|
||||||
|
location ^~ /.well-known/acme-challenge/ {
|
||||||
|
alias /data/eiblog/challenges/;
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
rewrite ^/(.*)$ https://deepzz.com/$1 permanent;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 博主原博客
|
|
||||||
server {
|
|
||||||
server_name blog.deepzz.com;
|
|
||||||
access_log /dev/null;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
rewrite ^/(.*)$ https://blog.deepzz.com/$1 permanent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 443;
|
|
||||||
server_name blog.deepzz.com;
|
|
||||||
|
|
||||||
add_header Strict-Transport-Security "max-age=31536000";
|
|
||||||
add_header X-Frame-Options deny;
|
|
||||||
add_header X-Content-Type-Options nosniff;
|
|
||||||
add_header X-Xss-Protection "1; mode=block;";
|
|
||||||
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass https://127.0.0.1:9010;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# example black list
|
||||||
|
#deny 195.154.211.220;
|
||||||
BIN
conf/scts/rsa/aviator.sct
Normal file
BIN
conf/scts/rsa/aviator.sct
Normal file
Binary file not shown.
BIN
conf/scts/rsa/digicert.sct
Normal file
BIN
conf/scts/rsa/digicert.sct
Normal file
Binary file not shown.
137
db.go
137
db.go
@@ -13,9 +13,7 @@ import (
|
|||||||
"github.com/eiblog/blackfriday"
|
"github.com/eiblog/blackfriday"
|
||||||
"github.com/eiblog/eiblog/setting"
|
"github.com/eiblog/eiblog/setting"
|
||||||
"github.com/eiblog/utils/logd"
|
"github.com/eiblog/utils/logd"
|
||||||
db "github.com/eiblog/utils/mgo"
|
"github.com/eiblog/utils/mgo"
|
||||||
"gopkg.in/mgo.v2"
|
|
||||||
"gopkg.in/mgo.v2/bson"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 数据库及表名
|
// 数据库及表名
|
||||||
@@ -62,40 +60,20 @@ var (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// 数据库加索引
|
// 数据库加索引
|
||||||
ms, c := db.Connect(DB, COLLECTION_ACCOUNT)
|
err := mgo.Index(DB, COLLECTION_ACCOUNT, []string{"username"})
|
||||||
index := mgo.Index{
|
if err != nil {
|
||||||
Key: []string{"username"},
|
|
||||||
Unique: true,
|
|
||||||
DropDups: true,
|
|
||||||
Background: true,
|
|
||||||
Sparse: true,
|
|
||||||
}
|
|
||||||
if err := c.EnsureIndex(index); err != nil {
|
|
||||||
logd.Fatal(err)
|
logd.Fatal(err)
|
||||||
}
|
}
|
||||||
ms.Close()
|
|
||||||
ms, c = db.Connect(DB, COLLECTION_ARTICLE)
|
err = mgo.Index(DB, COLLECTION_ARTICLE, []string{"id"})
|
||||||
index = mgo.Index{
|
if err != nil {
|
||||||
Key: []string{"id"},
|
|
||||||
Unique: true,
|
|
||||||
DropDups: true,
|
|
||||||
Background: true,
|
|
||||||
Sparse: true,
|
|
||||||
}
|
|
||||||
if err := c.EnsureIndex(index); err != nil {
|
|
||||||
logd.Fatal(err)
|
logd.Fatal(err)
|
||||||
}
|
}
|
||||||
index = mgo.Index{
|
|
||||||
Key: []string{"slug"},
|
err = mgo.Index(DB, COLLECTION_ARTICLE, []string{"slug"})
|
||||||
Unique: true,
|
if err != nil {
|
||||||
DropDups: true,
|
|
||||||
Background: true,
|
|
||||||
Sparse: true,
|
|
||||||
}
|
|
||||||
if err := c.EnsureIndex(index); err != nil {
|
|
||||||
logd.Fatal(err)
|
logd.Fatal(err)
|
||||||
}
|
}
|
||||||
ms.Close()
|
|
||||||
// 读取帐号信息
|
// 读取帐号信息
|
||||||
Ei = loadAccount()
|
Ei = loadAccount()
|
||||||
// 获取文章数据
|
// 获取文章数据
|
||||||
@@ -111,7 +89,7 @@ func init() {
|
|||||||
// 读取或初始化帐号信息
|
// 读取或初始化帐号信息
|
||||||
func loadAccount() (a *Account) {
|
func loadAccount() (a *Account) {
|
||||||
a = &Account{}
|
a = &Account{}
|
||||||
err := db.FindOne(DB, COLLECTION_ACCOUNT, bson.M{"username": setting.Conf.Account.Username}, a)
|
err := mgo.FindOne(DB, COLLECTION_ACCOUNT, mgo.M{"username": setting.Conf.Account.Username}, a)
|
||||||
// 初始化用户数据
|
// 初始化用户数据
|
||||||
if err == mgo.ErrNotFound {
|
if err == mgo.ErrNotFound {
|
||||||
a = &Account{
|
a = &Account{
|
||||||
@@ -127,7 +105,7 @@ func loadAccount() (a *Account) {
|
|||||||
a.BeiAn = setting.Conf.Blogger.BeiAn
|
a.BeiAn = setting.Conf.Blogger.BeiAn
|
||||||
a.BTitle = setting.Conf.Blogger.BTitle
|
a.BTitle = setting.Conf.Blogger.BTitle
|
||||||
a.Copyright = setting.Conf.Blogger.Copyright
|
a.Copyright = setting.Conf.Blogger.Copyright
|
||||||
err = db.Insert(DB, COLLECTION_ACCOUNT, a)
|
err = mgo.Insert(DB, COLLECTION_ACCOUNT, a)
|
||||||
generateTopic()
|
generateTopic()
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
logd.Fatal(err)
|
logd.Fatal(err)
|
||||||
@@ -139,7 +117,7 @@ func loadAccount() (a *Account) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadArticles() (artcs SortArticles) {
|
func loadArticles() (artcs SortArticles) {
|
||||||
err := db.FindAll(DB, COLLECTION_ARTICLE, bson.M{"isdraft": false, "deletetime": bson.M{"$eq": time.Time{}}}, &artcs)
|
err := mgo.FindAll(DB, COLLECTION_ARTICLE, mgo.M{"isdraft": false, "deletetime": mgo.M{"$eq": time.Time{}}}, &artcs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logd.Fatal(err)
|
logd.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -149,13 +127,13 @@ func loadArticles() (artcs SortArticles) {
|
|||||||
GenerateExcerptAndRender(v)
|
GenerateExcerptAndRender(v)
|
||||||
Ei.MapArticles[v.Slug] = v
|
Ei.MapArticles[v.Slug] = v
|
||||||
// 分析文章
|
// 分析文章
|
||||||
if v.ID < setting.Conf.StartID {
|
if v.ID < setting.Conf.General.StartID {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
v.Prev = artcs[i-1]
|
v.Prev = artcs[i-1]
|
||||||
}
|
}
|
||||||
if artcs[i+1].ID >= setting.Conf.StartID {
|
if artcs[i+1].ID >= setting.Conf.General.StartID {
|
||||||
v.Next = artcs[i+1]
|
v.Next = artcs[i+1]
|
||||||
}
|
}
|
||||||
ManageTagsArticle(v, false, ADD)
|
ManageTagsArticle(v, false, ADD)
|
||||||
@@ -209,26 +187,26 @@ func generateMarkdown() {
|
|||||||
// init account: generate blogroll and about page
|
// init account: generate blogroll and about page
|
||||||
func generateTopic() {
|
func generateTopic() {
|
||||||
about := &Article{
|
about := &Article{
|
||||||
ID: db.NextVal(DB, COUNTER_ARTICLE),
|
ID: mgo.NextVal(DB, COUNTER_ARTICLE),
|
||||||
Author: setting.Conf.Account.Username,
|
Author: setting.Conf.Account.Username,
|
||||||
Title: "关于",
|
Title: "关于",
|
||||||
Slug: "about",
|
Slug: "about",
|
||||||
CreateTime: time.Now(),
|
CreateTime: time.Time{},
|
||||||
UpdateTime: time.Time{},
|
UpdateTime: time.Time{},
|
||||||
}
|
}
|
||||||
blogroll := &Article{
|
blogroll := &Article{
|
||||||
ID: db.NextVal(DB, COUNTER_ARTICLE),
|
ID: mgo.NextVal(DB, COUNTER_ARTICLE),
|
||||||
Author: setting.Conf.Account.Username,
|
Author: setting.Conf.Account.Username,
|
||||||
Title: "友情链接",
|
Title: "友情链接",
|
||||||
Slug: "blogroll",
|
Slug: "blogroll",
|
||||||
UpdateTime: time.Now(),
|
|
||||||
CreateTime: time.Time{},
|
CreateTime: time.Time{},
|
||||||
|
UpdateTime: time.Time{},
|
||||||
}
|
}
|
||||||
err := db.Insert(DB, COLLECTION_ARTICLE, blogroll)
|
err := mgo.Insert(DB, COLLECTION_ARTICLE, blogroll)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logd.Fatal(err)
|
logd.Fatal(err)
|
||||||
}
|
}
|
||||||
err = db.Insert(DB, COLLECTION_ARTICLE, about)
|
err = mgo.Insert(DB, COLLECTION_ARTICLE, about)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logd.Fatal(err)
|
logd.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -244,7 +222,7 @@ func renderPage(md []byte) []byte {
|
|||||||
func PageList(p, n int) (prev int, next int, artcs []*Article) {
|
func PageList(p, n int) (prev int, next int, artcs []*Article) {
|
||||||
var l int
|
var l int
|
||||||
for l = len(Ei.Articles); l > 0; l-- {
|
for l = len(Ei.Articles); l > 0; l-- {
|
||||||
if Ei.Articles[l-1].ID >= setting.Conf.StartID {
|
if Ei.Articles[l-1].ID >= setting.Conf.General.StartID {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,6 +251,7 @@ func PageList(p, n int) (prev int, next int, artcs []*Article) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 管理 tag
|
||||||
func ManageTagsArticle(artc *Article, s bool, do string) {
|
func ManageTagsArticle(artc *Article, s bool, do string) {
|
||||||
switch do {
|
switch do {
|
||||||
case ADD:
|
case ADD:
|
||||||
@@ -297,6 +276,7 @@ func ManageTagsArticle(artc *Article, s bool, do string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 管理专题
|
||||||
func ManageSeriesArticle(artc *Article, s bool, do string) {
|
func ManageSeriesArticle(artc *Article, s bool, do string) {
|
||||||
switch do {
|
switch do {
|
||||||
case ADD:
|
case ADD:
|
||||||
@@ -305,7 +285,6 @@ func ManageSeriesArticle(artc *Article, s bool, do string) {
|
|||||||
Ei.Series[i].Articles = append(Ei.Series[i].Articles, artc)
|
Ei.Series[i].Articles = append(Ei.Series[i].Articles, artc)
|
||||||
if s {
|
if s {
|
||||||
sort.Sort(Ei.Series[i].Articles)
|
sort.Sort(Ei.Series[i].Articles)
|
||||||
Ei.CH <- SERIES_MD
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -316,7 +295,6 @@ func ManageSeriesArticle(artc *Article, s bool, do string) {
|
|||||||
for j, v := range serie.Articles {
|
for j, v := range serie.Articles {
|
||||||
if v == artc {
|
if v == artc {
|
||||||
Ei.Series[i].Articles = append(Ei.Series[i].Articles[0:j], Ei.Series[i].Articles[j+1:]...)
|
Ei.Series[i].Articles = append(Ei.Series[i].Articles[0:j], Ei.Series[i].Articles[j+1:]...)
|
||||||
Ei.CH <- SERIES_MD
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -325,6 +303,7 @@ func ManageSeriesArticle(artc *Article, s bool, do string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 管理归档
|
||||||
func ManageArchivesArticle(artc *Article, s bool, do string) {
|
func ManageArchivesArticle(artc *Article, s bool, do string) {
|
||||||
switch do {
|
switch do {
|
||||||
case ADD:
|
case ADD:
|
||||||
@@ -337,7 +316,6 @@ func ManageArchivesArticle(artc *Article, s bool, do string) {
|
|||||||
Ei.Archives[i].Articles = append(Ei.Archives[i].Articles, artc)
|
Ei.Archives[i].Articles = append(Ei.Archives[i].Articles, artc)
|
||||||
if s {
|
if s {
|
||||||
sort.Sort(Ei.Archives[i].Articles)
|
sort.Sort(Ei.Archives[i].Articles)
|
||||||
Ei.CH <- ARCHIVE_MD
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -352,7 +330,9 @@ func ManageArchivesArticle(artc *Article, s bool, do string) {
|
|||||||
for j, v := range archive.Articles {
|
for j, v := range archive.Articles {
|
||||||
if v == artc {
|
if v == artc {
|
||||||
Ei.Archives[i].Articles = append(Ei.Archives[i].Articles[0:j], Ei.Archives[i].Articles[j+1:]...)
|
Ei.Archives[i].Articles = append(Ei.Archives[i].Articles[0:j], Ei.Archives[i].Articles[j+1:]...)
|
||||||
Ei.CH <- ARCHIVE_MD
|
if len(Ei.Archives[i].Articles) == 0 {
|
||||||
|
Ei.Archives = append(Ei.Archives[:i], Ei.Archives[i+1:]...)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -362,18 +342,19 @@ func ManageArchivesArticle(artc *Article, s bool, do string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 渲染markdown操作和截取摘要操作
|
// 渲染markdown操作和截取摘要操作
|
||||||
var reg = regexp.MustCompile(setting.Conf.Identifier)
|
var reg = regexp.MustCompile(setting.Conf.General.Identifier)
|
||||||
|
|
||||||
// header
|
// header
|
||||||
var regH = regexp.MustCompile("</nav></div>")
|
var regH = regexp.MustCompile("</nav></div>")
|
||||||
|
|
||||||
func GenerateExcerptAndRender(artc *Article) {
|
func GenerateExcerptAndRender(artc *Article) {
|
||||||
if strings.HasPrefix(artc.Content, setting.Conf.Description) {
|
if strings.HasPrefix(artc.Content, setting.Conf.General.DescPrefix) {
|
||||||
index := strings.Index(artc.Content, "\r\n")
|
index := strings.Index(artc.Content, "\r\n")
|
||||||
artc.Desc = IgnoreHtmlTag(artc.Content[len(setting.Conf.Description):index])
|
artc.Desc = IgnoreHtmlTag(artc.Content[len(setting.Conf.General.DescPrefix):index])
|
||||||
artc.Content = artc.Content[index:]
|
artc.Content = artc.Content[index:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查找目录
|
||||||
content := renderPage([]byte(artc.Content))
|
content := renderPage([]byte(artc.Content))
|
||||||
index := regH.FindIndex(content)
|
index := regH.FindIndex(content)
|
||||||
if index != nil {
|
if index != nil {
|
||||||
@@ -387,7 +368,7 @@ func GenerateExcerptAndRender(artc *Article) {
|
|||||||
artc.Excerpt = IgnoreHtmlTag(artc.Content[0:index[0]])
|
artc.Excerpt = IgnoreHtmlTag(artc.Content[0:index[0]])
|
||||||
} else {
|
} else {
|
||||||
uc := []rune(artc.Content)
|
uc := []rune(artc.Content)
|
||||||
length := setting.Conf.Length
|
length := setting.Conf.General.Length
|
||||||
if len(uc) < length {
|
if len(uc) < length {
|
||||||
length = len(uc)
|
length = len(uc)
|
||||||
}
|
}
|
||||||
@@ -397,14 +378,14 @@ func GenerateExcerptAndRender(artc *Article) {
|
|||||||
|
|
||||||
// 读取草稿箱
|
// 读取草稿箱
|
||||||
func LoadDraft() (artcs SortArticles, err error) {
|
func LoadDraft() (artcs SortArticles, err error) {
|
||||||
err = db.FindAll(DB, COLLECTION_ARTICLE, bson.M{"isdraft": true}, &artcs)
|
err = mgo.FindAll(DB, COLLECTION_ARTICLE, mgo.M{"isdraft": true}, &artcs)
|
||||||
sort.Sort(artcs)
|
sort.Sort(artcs)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取回收箱
|
// 读取回收箱
|
||||||
func LoadTrash() (artcs SortArticles, err error) {
|
func LoadTrash() (artcs SortArticles, err error) {
|
||||||
err = db.FindAll(DB, COLLECTION_ARTICLE, bson.M{"deletetime": bson.M{"$ne": time.Time{}}}, &artcs)
|
err = mgo.FindAll(DB, COLLECTION_ARTICLE, mgo.M{"deletetime": mgo.M{"$ne": time.Time{}}}, &artcs)
|
||||||
sort.Sort(artcs)
|
sort.Sort(artcs)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -413,14 +394,16 @@ func LoadTrash() (artcs SortArticles, err error) {
|
|||||||
func AddArticle(artc *Article) error {
|
func AddArticle(artc *Article) error {
|
||||||
// 分配ID, 占位至起始id
|
// 分配ID, 占位至起始id
|
||||||
for {
|
for {
|
||||||
if id := db.NextVal(DB, COUNTER_ARTICLE); id < setting.Conf.StartID {
|
if id := mgo.NextVal(DB, COUNTER_ARTICLE); id < setting.Conf.General.StartID {
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
artc.ID = id
|
artc.ID = id
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !artc.IsDraft {
|
if !artc.IsDraft {
|
||||||
|
// 正式发布文章
|
||||||
defer GenerateExcerptAndRender(artc)
|
defer GenerateExcerptAndRender(artc)
|
||||||
Ei.MapArticles[artc.Slug] = artc
|
Ei.MapArticles[artc.Slug] = artc
|
||||||
Ei.Articles = append([]*Article{artc}, Ei.Articles...)
|
Ei.Articles = append([]*Article{artc}, Ei.Articles...)
|
||||||
@@ -434,7 +417,7 @@ func AddArticle(artc *Article) error {
|
|||||||
Ei.CH <- SERIES_MD
|
Ei.CH <- SERIES_MD
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return db.Insert(DB, COLLECTION_ARTICLE, artc)
|
return mgo.Insert(DB, COLLECTION_ARTICLE, artc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除文章,移入回收箱
|
// 删除文章,移入回收箱
|
||||||
@@ -449,7 +432,7 @@ func DelArticles(ids ...int32) error {
|
|||||||
ManageTagsArticle(artc, false, DELETE)
|
ManageTagsArticle(artc, false, DELETE)
|
||||||
ManageSeriesArticle(artc, false, DELETE)
|
ManageSeriesArticle(artc, false, DELETE)
|
||||||
ManageArchivesArticle(artc, false, DELETE)
|
ManageArchivesArticle(artc, false, DELETE)
|
||||||
err := UpdateArticle(bson.M{"id": id}, bson.M{"$set": bson.M{"deletetime": time.Now()}})
|
err := UpdateArticle(mgo.M{"id": id}, mgo.M{"$set": mgo.M{"deletetime": time.Now()}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -460,6 +443,7 @@ func DelArticles(ids ...int32) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从链表里删除文章
|
||||||
func DelFromLinkedList(artc *Article) {
|
func DelFromLinkedList(artc *Article) {
|
||||||
if artc.Prev == nil && artc.Next != nil {
|
if artc.Prev == nil && artc.Next != nil {
|
||||||
artc.Next.Prev = nil
|
artc.Next.Prev = nil
|
||||||
@@ -471,12 +455,13 @@ func DelFromLinkedList(artc *Article) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将文章添加到链表
|
||||||
func AddToLinkedList(id int32) {
|
func AddToLinkedList(id int32) {
|
||||||
i, artc := GetArticle(id)
|
i, artc := GetArticle(id)
|
||||||
if i == 0 && Ei.Articles[i+1].ID >= setting.Conf.StartID {
|
if i == 0 && Ei.Articles[i+1].ID >= setting.Conf.General.StartID {
|
||||||
artc.Next = Ei.Articles[i+1]
|
artc.Next = Ei.Articles[i+1]
|
||||||
Ei.Articles[i+1].Prev = artc
|
Ei.Articles[i+1].Prev = artc
|
||||||
} else if i > 0 && Ei.Articles[i-1].ID >= setting.Conf.StartID {
|
} else if i > 0 && Ei.Articles[i-1].ID >= setting.Conf.General.StartID {
|
||||||
artc.Prev = Ei.Articles[i-1]
|
artc.Prev = Ei.Articles[i-1]
|
||||||
if Ei.Articles[i-1].Next != nil {
|
if Ei.Articles[i-1].Next != nil {
|
||||||
artc.Next = Ei.Articles[i-1].Next
|
artc.Next = Ei.Articles[i-1].Next
|
||||||
@@ -498,37 +483,37 @@ func GetArticle(id int32) (int, *Article) {
|
|||||||
|
|
||||||
// 定时清除回收箱文章
|
// 定时清除回收箱文章
|
||||||
func timer() {
|
func timer() {
|
||||||
delT := time.NewTicker(time.Duration(setting.Conf.Clean) * time.Hour)
|
delT := time.NewTicker(time.Duration(setting.Conf.General.Clean) * time.Hour)
|
||||||
for {
|
for {
|
||||||
<-delT.C
|
<-delT.C
|
||||||
db.Remove(DB, COLLECTION_ARTICLE, bson.M{"deletetime": bson.M{"$gt": time.Time{}, "$lt": time.Now().Add(time.Duration(setting.Conf.Trash) * time.Hour)}})
|
mgo.Remove(DB, COLLECTION_ARTICLE, mgo.M{"deletetime": mgo.M{"$gt": time.Time{}, "$lt": time.Now().Add(time.Duration(setting.Conf.General.Trash) * time.Hour)}})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 操作帐号字段
|
// 操作帐号字段
|
||||||
func UpdateAccountField(M bson.M) error {
|
func UpdateAccountField(M mgo.M) error {
|
||||||
return db.Update(DB, COLLECTION_ACCOUNT, bson.M{"username": Ei.Username}, M)
|
return mgo.Update(DB, COLLECTION_ACCOUNT, mgo.M{"username": Ei.Username}, M)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除草稿箱或回收箱,永久删除
|
// 删除草稿箱或回收箱,永久删除
|
||||||
func RemoveArticle(id int32) error {
|
func RemoveArticle(id int32) error {
|
||||||
return db.Remove(DB, COLLECTION_ARTICLE, bson.M{"id": id})
|
return mgo.Remove(DB, COLLECTION_ARTICLE, mgo.M{"id": id})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 恢复删除文章到草稿箱
|
// 恢复删除文章到草稿箱
|
||||||
func RecoverArticle(id int32) error {
|
func RecoverArticle(id int32) error {
|
||||||
return db.Update(DB, COLLECTION_ARTICLE, bson.M{"id": id}, bson.M{"$set": bson.M{"deletetime": time.Time{}, "isdraft": true}})
|
return mgo.Update(DB, COLLECTION_ARTICLE, mgo.M{"id": id}, mgo.M{"$set": mgo.M{"deletetime": time.Time{}, "isdraft": true}})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新文章
|
// 更新文章
|
||||||
func UpdateArticle(query, update interface{}) error {
|
func UpdateArticle(query, update interface{}) error {
|
||||||
return db.Update(DB, COLLECTION_ARTICLE, query, update)
|
return mgo.Update(DB, COLLECTION_ARTICLE, query, update)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 编辑文档
|
// 编辑文档
|
||||||
func QueryArticle(id int32) *Article {
|
func QueryArticle(id int32) *Article {
|
||||||
artc := &Article{}
|
artc := &Article{}
|
||||||
if err := db.FindOne(DB, COLLECTION_ARTICLE, bson.M{"id": id}, artc); err != nil {
|
if err := mgo.FindOne(DB, COLLECTION_ARTICLE, mgo.M{"id": id}, artc); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return artc
|
return artc
|
||||||
@@ -536,17 +521,17 @@ func QueryArticle(id int32) *Article {
|
|||||||
|
|
||||||
// 添加专题
|
// 添加专题
|
||||||
func AddSerie(name, slug, desc string) error {
|
func AddSerie(name, slug, desc string) error {
|
||||||
serie := &Serie{db.NextVal(DB, COUNTER_SERIE), name, slug, desc, time.Now(), nil}
|
serie := &Serie{mgo.NextVal(DB, COUNTER_SERIE), name, slug, desc, time.Now(), nil}
|
||||||
Ei.Series = append(Ei.Series, serie)
|
Ei.Series = append(Ei.Series, serie)
|
||||||
sort.Sort(Ei.Series)
|
sort.Sort(Ei.Series)
|
||||||
Ei.CH <- SERIES_MD
|
Ei.CH <- SERIES_MD
|
||||||
return UpdateAccountField(bson.M{"$addToSet": bson.M{"blogger.series": serie}})
|
return UpdateAccountField(mgo.M{"$addToSet": mgo.M{"blogger.series": serie}})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新专题
|
// 更新专题
|
||||||
func UpdateSerie(serie *Serie) error {
|
func UpdateSerie(serie *Serie) error {
|
||||||
Ei.CH <- SERIES_MD
|
Ei.CH <- SERIES_MD
|
||||||
return db.Update(DB, COLLECTION_ACCOUNT, bson.M{"username": Ei.Username, "blogger.series.id": serie.ID}, bson.M{"$set": bson.M{"blogger.series.$": serie}})
|
return mgo.Update(DB, COLLECTION_ACCOUNT, mgo.M{"username": Ei.Username, "blogger.series.id": serie.ID}, mgo.M{"$set": mgo.M{"blogger.series.$": serie}})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除专题
|
// 删除专题
|
||||||
@@ -556,7 +541,7 @@ func DelSerie(id int32) error {
|
|||||||
if len(serie.Articles) > 0 {
|
if len(serie.Articles) > 0 {
|
||||||
return fmt.Errorf("请删除该专题下的所有文章")
|
return fmt.Errorf("请删除该专题下的所有文章")
|
||||||
}
|
}
|
||||||
err := UpdateAccountField(bson.M{"$pull": bson.M{"blogger.series": bson.M{"id": id}}})
|
err := UpdateAccountField(mgo.M{"$pull": mgo.M{"blogger.series": mgo.M{"id": id}}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -580,24 +565,24 @@ func QuerySerie(id int32) *Serie {
|
|||||||
|
|
||||||
// 后台分页
|
// 后台分页
|
||||||
func PageListBack(se int, kw string, draft, del bool, p, n int) (max int, artcs []*Article) {
|
func PageListBack(se int, kw string, draft, del bool, p, n int) (max int, artcs []*Article) {
|
||||||
M := bson.M{}
|
M := mgo.M{}
|
||||||
if draft {
|
if draft {
|
||||||
M["isdraft"] = true
|
M["isdraft"] = true
|
||||||
} else if del {
|
} else if del {
|
||||||
M["deletetime"] = bson.M{"$ne": time.Time{}}
|
M["deletetime"] = mgo.M{"$ne": time.Time{}}
|
||||||
} else {
|
} else {
|
||||||
M["isdraft"] = false
|
M["isdraft"] = false
|
||||||
M["deletetime"] = bson.M{"$eq": time.Time{}}
|
M["deletetime"] = mgo.M{"$eq": time.Time{}}
|
||||||
if se > 0 {
|
if se > 0 {
|
||||||
M["serieid"] = se
|
M["serieid"] = se
|
||||||
}
|
}
|
||||||
if kw != "" {
|
if kw != "" {
|
||||||
M["title"] = bson.M{"$regex": kw, "$options": "$i"}
|
M["title"] = mgo.M{"$regex": kw, "$options": "$i"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ms, c := db.Connect(DB, COLLECTION_ARTICLE)
|
ms, c := mgo.Connect(DB, COLLECTION_ARTICLE)
|
||||||
defer ms.Close()
|
defer ms.Close()
|
||||||
err := c.Find(M).Select(bson.M{"content": 0}).Sort("-createtime").Limit(n).Skip((p - 1) * n).All(&artcs)
|
err := c.Find(M).Select(mgo.M{"content": 0}).Sort("-createtime").Limit(n).Skip((p - 1) * n).All(&artcs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logd.Error(err)
|
logd.Error(err)
|
||||||
}
|
}
|
||||||
|
|||||||
199
disqus.go
199
disqus.go
@@ -4,17 +4,21 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/deepzz0/logd"
|
||||||
"github.com/eiblog/eiblog/setting"
|
"github.com/eiblog/eiblog/setting"
|
||||||
"github.com/eiblog/utils/logd"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type result struct {
|
var ErrDisqusConfig = errors.New("disqus config incorrect")
|
||||||
|
|
||||||
|
// 定时获取所有文章评论数量
|
||||||
|
type postsCountResp struct {
|
||||||
Code int
|
Code int
|
||||||
Response []struct {
|
Response []struct {
|
||||||
Id string
|
Id string
|
||||||
@@ -23,16 +27,25 @@ type result struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func PostsCount() {
|
func PostsCount() error {
|
||||||
if setting.Conf.Disqus.PostsCount == "" || setting.Conf.Disqus.PublicKey == "" || setting.Conf.Disqus.ShortName == "" {
|
if setting.Conf.Disqus.PostsCount == "" ||
|
||||||
return
|
setting.Conf.Disqus.PublicKey == "" ||
|
||||||
|
setting.Conf.Disqus.ShortName == "" {
|
||||||
|
return ErrDisqusConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
time.AfterFunc(time.Duration(setting.Conf.Disqus.Interval)*time.Hour, func() {
|
||||||
|
err := PostsCount()
|
||||||
|
if err != nil {
|
||||||
|
logd.Error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
baseUrl := setting.Conf.Disqus.PostsCount +
|
baseUrl := setting.Conf.Disqus.PostsCount +
|
||||||
"?api_key=" + setting.Conf.Disqus.PublicKey +
|
"?api_key=" + setting.Conf.Disqus.PublicKey +
|
||||||
"&forum=" + setting.Conf.Disqus.ShortName + "&"
|
"&forum=" + setting.Conf.Disqus.ShortName + "&"
|
||||||
var count, index int
|
var count, index int
|
||||||
for index < len(Ei.Articles) {
|
for index < len(Ei.Articles) {
|
||||||
logd.Debugf("count=====%d, index=======%d, length=======%d, bool=========%t\n", count, index, len(Ei.Articles), index < len(Ei.Articles) && count < 50)
|
|
||||||
var threads []string
|
var threads []string
|
||||||
for ; index < len(Ei.Articles) && count < 50; index++ {
|
for ; index < len(Ei.Articles) && count < 50; index++ {
|
||||||
artc := Ei.Articles[index]
|
artc := Ei.Articles[index]
|
||||||
@@ -43,26 +56,24 @@ func PostsCount() {
|
|||||||
url := baseUrl + strings.Join(threads, "&")
|
url := baseUrl + strings.Join(threads, "&")
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logd.Error(err)
|
return err
|
||||||
break
|
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
b, err := ioutil.ReadAll(resp.Body)
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logd.Error(err)
|
return err
|
||||||
break
|
|
||||||
}
|
}
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
logd.Error(string(b))
|
return errors.New(string(b))
|
||||||
break
|
|
||||||
}
|
}
|
||||||
rst := result{}
|
|
||||||
err = json.Unmarshal(b, &rst)
|
result := &postsCountResp{}
|
||||||
|
err = json.Unmarshal(b, result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logd.Error(err)
|
return err
|
||||||
break
|
|
||||||
}
|
}
|
||||||
for _, v := range rst.Response {
|
for _, v := range result.Response {
|
||||||
i := strings.Index(v.Identifiers[0], "-")
|
i := strings.Index(v.Identifiers[0], "-")
|
||||||
artc := Ei.MapArticles[v.Identifiers[0][i+1:]]
|
artc := Ei.MapArticles[v.Identifiers[0][i+1:]]
|
||||||
if artc != nil {
|
if artc != nil {
|
||||||
@@ -71,60 +82,66 @@ func PostsCount() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
time.AfterFunc(time.Duration(setting.Conf.Disqus.Interval)*time.Hour, PostsCount)
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type postsList struct {
|
// 获取文章评论列表
|
||||||
|
type postsListResp struct {
|
||||||
Cursor struct {
|
Cursor struct {
|
||||||
HasNext bool
|
HasNext bool
|
||||||
Next string
|
Next string
|
||||||
}
|
}
|
||||||
Code int
|
Code int
|
||||||
Response []struct {
|
Response []postDetail
|
||||||
Parent int
|
|
||||||
Id string
|
|
||||||
CreatedAt string
|
|
||||||
Message string
|
|
||||||
Author struct {
|
|
||||||
Name string
|
|
||||||
ProfileUrl string
|
|
||||||
Avatar struct {
|
|
||||||
Cache string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Thread string
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func PostsList(slug, cursor string) *postsList {
|
type postDetail struct {
|
||||||
if setting.Conf.Disqus.PostsList == "" || setting.Conf.Disqus.PublicKey == "" || setting.Conf.Disqus.ShortName == "" {
|
Parent int
|
||||||
return nil
|
Id string
|
||||||
|
CreatedAt string
|
||||||
|
Message string
|
||||||
|
IsDeleted bool
|
||||||
|
Author struct {
|
||||||
|
Name string
|
||||||
|
ProfileUrl string
|
||||||
|
Avatar struct {
|
||||||
|
Cache string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Thread string
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostsList(slug, cursor string) (*postsListResp, error) {
|
||||||
|
if setting.Conf.Disqus.PostsList == "" ||
|
||||||
|
setting.Conf.Disqus.PublicKey == "" ||
|
||||||
|
setting.Conf.Disqus.ShortName == "" {
|
||||||
|
return nil, ErrDisqusConfig
|
||||||
|
}
|
||||||
|
|
||||||
url := setting.Conf.Disqus.PostsList + "?limit=50&api_key=" +
|
url := setting.Conf.Disqus.PostsList + "?limit=50&api_key=" +
|
||||||
setting.Conf.Disqus.PublicKey + "&forum=" + setting.Conf.Disqus.ShortName +
|
setting.Conf.Disqus.PublicKey + "&forum=" + setting.Conf.Disqus.ShortName +
|
||||||
"&cursor=" + cursor + "&thread:ident=post-" + slug
|
"&cursor=" + cursor + "&thread:ident=post-" + slug
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logd.Error(err)
|
return nil, err
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
b, err := ioutil.ReadAll(resp.Body)
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logd.Error(err)
|
return nil, err
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
logd.Error(string(b))
|
return nil, errors.New(string(b))
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
pl := &postsList{}
|
|
||||||
err = json.Unmarshal(b, pl)
|
result := &postsListResp{}
|
||||||
|
err = json.Unmarshal(b, result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logd.Error(err)
|
return nil, err
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
return pl
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type PostCreate struct {
|
type PostCreate struct {
|
||||||
@@ -138,16 +155,17 @@ type PostCreate struct {
|
|||||||
UserAgent string `json:"user_agent"`
|
UserAgent string `json:"user_agent"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PostResponse struct {
|
type postCreateResp struct {
|
||||||
Code int `json:"code"`
|
Code int
|
||||||
Response struct {
|
Response postDetail
|
||||||
Id string `json:"id"`
|
|
||||||
} `json:"response"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func PostComment(pc *PostCreate) string {
|
// 评论文章
|
||||||
if setting.Conf.Disqus.PostsList == "" || setting.Conf.Disqus.PublicKey == "" || setting.Conf.Disqus.ShortName == "" {
|
func PostComment(pc *PostCreate) (*postCreateResp, error) {
|
||||||
return ""
|
if setting.Conf.Disqus.PostsList == "" ||
|
||||||
|
setting.Conf.Disqus.PublicKey == "" ||
|
||||||
|
setting.Conf.Disqus.ShortName == "" {
|
||||||
|
return nil, ErrDisqusConfig
|
||||||
}
|
}
|
||||||
url := setting.Conf.Disqus.PostCreate +
|
url := setting.Conf.Disqus.PostCreate +
|
||||||
"?api_key=E8Uh5l5fHZ6gD8U3KycjAIAk46f68Zw7C6eW8WSjZvCLXebZ7p0r1yrYDrLilk2F" +
|
"?api_key=E8Uh5l5fHZ6gD8U3KycjAIAk46f68Zw7C6eW8WSjZvCLXebZ7p0r1yrYDrLilk2F" +
|
||||||
@@ -157,31 +175,74 @@ func PostComment(pc *PostCreate) string {
|
|||||||
|
|
||||||
request, err := http.NewRequest("POST", url, nil)
|
request, err := http.NewRequest("POST", url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logd.Error(err)
|
return nil, err
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
request.Header.Set("Referer", "https://disqus.com")
|
request.Header.Set("Referer", "https://disqus.com")
|
||||||
resp, err := http.DefaultClient.Do(request)
|
resp, err := http.DefaultClient.Do(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logd.Error(err)
|
return nil, err
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
b, err := ioutil.ReadAll(resp.Body)
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logd.Error(err)
|
return nil, err
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
logd.Error(string(b))
|
return nil, errors.New(string(b))
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
pr := &PostResponse{}
|
result := &postCreateResp{}
|
||||||
err = json.Unmarshal(b, pr)
|
err = json.Unmarshal(b, result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logd.Error(err)
|
return nil, err
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
logd.Print(pr.Response.Id)
|
return result, nil
|
||||||
return pr.Response.Id
|
}
|
||||||
|
|
||||||
|
// 批准评论通过
|
||||||
|
type approvedResp struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Response []struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
} `json:"response"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostApprove(post string) error {
|
||||||
|
if setting.Conf.Disqus.PostsList == "" ||
|
||||||
|
setting.Conf.Disqus.PublicKey == "" ||
|
||||||
|
setting.Conf.Disqus.ShortName == "" {
|
||||||
|
return ErrDisqusConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
url := setting.Conf.Disqus.PostApprove +
|
||||||
|
"?api_key=" + setting.Conf.Disqus.PublicKey +
|
||||||
|
"&access_token=" + setting.Conf.Disqus.AccessToken +
|
||||||
|
"&post=" + post
|
||||||
|
request, err := http.NewRequest("POST", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
request.Header.Set("Referer", "https://disqus.com")
|
||||||
|
resp, err := http.DefaultClient.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return errors.New(string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &approvedResp{}
|
||||||
|
err = json.Unmarshal(b, result)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ func TestPostComment(t *testing.T) {
|
|||||||
AuthorName: "deepzz",
|
AuthorName: "deepzz",
|
||||||
}
|
}
|
||||||
|
|
||||||
id := PostComment(pc)
|
id, err := PostComment(pc)
|
||||||
if id == "" {
|
if err != nil {
|
||||||
t.Error("post failed")
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t.Log("post success")
|
t.Log("post success", id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- /data/eiblog/logdata:/eiblog/logdata
|
- /data/eiblog/logdata:/eiblog/logdata
|
||||||
- /data/eiblog/conf:/eiblog/conf
|
- /data/eiblog/conf:/eiblog/conf
|
||||||
- /data/eiblog/static:/eiblog/static
|
|
||||||
- /data/eiblog/views:/eiblog/views
|
|
||||||
links:
|
links:
|
||||||
- elasticsearch
|
- elasticsearch
|
||||||
- mongodb
|
- mongodb
|
||||||
|
|||||||
69
docs/autocert.md
Normal file
69
docs/autocert.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
### 证书自动更新
|
||||||
|
|
||||||
|
本博客证书自动更新有两种方式:
|
||||||
|
|
||||||
|
* [acme/autocert](https://github.com/golang/crypto/tree/master/acme/autocert),博客内部集成,通过 tls-sni 验证,实现全自动更新证书,一键开启关闭。请在裸服务器的情况下使用(不要使用代理)。单证书
|
||||||
|
* [acme.sh](https://github.com/Neilpang/acme.sh),强大的 acme 脚本,多种自动更新证书方式,满足你各方面的需求。双证书
|
||||||
|
|
||||||
|
#### 方式一
|
||||||
|
什么是 autocert,简单点,你只需要两步操作:
|
||||||
|
|
||||||
|
1. 将域名解析到你的服务器。
|
||||||
|
2. 在服务器上运行开启 autocert 功能的程序(这里不需要配置证书),需要占用 443 端口。
|
||||||
|
|
||||||
|
其它过程你不需要过问,即会完成自动申请证书,自动更新证书的功能(默认 30 天)。这个是在 tcp/ip 层的操作,对用户完全透明,非常棒。
|
||||||
|
|
||||||
|
一键开启 autocert 功能,只需修改 `conf/app.yml` 文件内容:
|
||||||
|
|
||||||
|
```
|
||||||
|
# 运行模式
|
||||||
|
mode:
|
||||||
|
# http server
|
||||||
|
enablehttp: true
|
||||||
|
httpport: 9000
|
||||||
|
# https server
|
||||||
|
enablehttps: true # 必须开启
|
||||||
|
autocert: false # autocert 功能开关
|
||||||
|
httpsport: 9001
|
||||||
|
certfile:
|
||||||
|
keyfile:
|
||||||
|
domain: deepzz.com # 申请证书的域名,也是博客的域名
|
||||||
|
```
|
||||||
|
|
||||||
|
首先,使用 HTTPS 必须启用 `enablehttps`,它有两个作用:
|
||||||
|
|
||||||
|
* 如果 `enablehttp` 开启,会自动 301 重定向到 https。
|
||||||
|
* 作为开启 autocert 的前提条件。
|
||||||
|
|
||||||
|
其次, `autocert` 是否开启也有两个作用:
|
||||||
|
|
||||||
|
* false,服务器将使用 `httpsport`、`certfile`、`keyfile` 作为参数启动 https 服务器。
|
||||||
|
* true,服务器直接使用 443 端口启动 https 服务器,并且自动申请证书,且在证书只有 30 天有效期时自动更新证书。域名为 *运行模式* 下的 mode->domain。
|
||||||
|
|
||||||
|
#### 方式二
|
||||||
|
|
||||||
|
使用方式二,你需要了解 acme.sh 的具体使用方式,非常简单。选择适合自己的方式实现自动更新证书。
|
||||||
|
|
||||||
|
博主,这里实现了 aliyun dns 的自动验证,自动更新证书。详情参见 Makefile->gencert。这里实现了自动申请 ecc、rsa 双证书,并且自动申请 scts,自动安装,自动更新。
|
||||||
|
|
||||||
|
基本流程如下:
|
||||||
|
|
||||||
|
1. 创建相关目录:`/data/eiblog/conf/ssl`、`/data/eiblog/conf/scts/rsa`、`/data/eiblog/conf/scts/ecc`。
|
||||||
|
2. 自动下载安装 acme.sh 脚本。
|
||||||
|
3. 自动申请 RSA 证书并且自动获取 scts,并且自动安装到指定位置。
|
||||||
|
4. 自动申请 ECC 证书并且自动获取 scts,并且自动安装到指定位置。
|
||||||
|
|
||||||
|
##### 使用方式
|
||||||
|
|
||||||
|
导出环境变量,Aliyun dns 的环境变量为:
|
||||||
|
|
||||||
|
```
|
||||||
|
export Ali_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
|
||||||
|
export Ali_Secret="jlsdflanljkljlfdsaklkjflsa"
|
||||||
|
```
|
||||||
|
|
||||||
|
执行命令:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ make gencert -cn=common_name -sans="-d example.com -d example1.com"
|
||||||
|
```
|
||||||
195
docs/install.md
195
docs/install.md
@@ -1,28 +1,25 @@
|
|||||||
### 安装
|
### 安装
|
||||||
1、`Eiblog`提供多个平台的压缩包下载,可到[Eiblog release](https://github.com/eiblog/eiblog/releases)选择相应版本和平台下载。也可通过:
|
1、`Eiblog` 提供多个平台的压缩包下载,可到 [Eiblog release](https://github.com/eiblog/eiblog/releases) 选择相应版本和平台下载。也可通过:
|
||||||
``` sh
|
``` sh
|
||||||
$ curl -L https://github.com/eiblog/eiblog/releases/download/v1.0.0/eiblog-v1.0.0.`uname -s | tr '[A-Z]' '[a-z]'`-amd64.tar.gz > eiblog-v1.0.0.`uname -s | tr '[A-Z]' '[a-z]'`-amd64.tar.gz
|
$ curl -L https://github.com/eiblog/eiblog/releases/download/v1.0.0/eiblog-v1.0.0.`uname -s | tr '[A-Z]' '[a-z]'`-amd64.tar.gz > eiblog-v1.0.0.`uname -s | tr '[A-Z]' '[a-z]'`-amd64.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
2、如果有幸你也是`Gopher`,相信你会亲自动手,你可以通过:
|
2、如果有幸你也是 `Gopher`,相信你会亲自动手,你可以通过:
|
||||||
``` sh
|
``` sh
|
||||||
$ go get https://github.com/eiblog/eiblog
|
$ git clone https://github.com/eiblog/eiblog.git
|
||||||
```
|
```
|
||||||
进行源码编译二进制文件运行。
|
进行源码编译二进制文件运行。
|
||||||
|
|
||||||
3、如果你对`docker`技术也有研究的话,你也可以通过`docker`来安装:
|
3、如果你对 `docker` 技术也有研究的话,你也可以通过 `docker` 来安装:
|
||||||
``` sh
|
``` sh
|
||||||
$ docker pull registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog:v1.2.0
|
$ docker pull registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog:v1.2.0
|
||||||
|
|
||||||
```
|
```
|
||||||
镜像内部只提供了`eiblog`的二进制文件,因为其它内容定制化的需求过高。所以需要将`conf`、`static`、`views`目录映射出来,后面会具体说到。
|
`注意`,镜像内部没有提供 conf 文件夹内的配置内容,因为该内容定制化的需求过高。所以需要将 `conf` 目录映射出来,后面会具体说到。
|
||||||
|
|
||||||
### 本地测试
|
### 本地测试
|
||||||
在我们下载好可执行程序之后,我们可以开始本地测试的工作了。
|
采用二进制包进行测试,在下载好可执行程序之后,我们可以开始本地测试的工作了。本地测试需要搭建两个服务 `mongodb` (必须)和 `elasticsearch2.4.1`(可选,搜索服务不可用)。
|
||||||
|
|
||||||
本地测试需要搭建两个服务`mongodb`和`elasticsearch2.4.1`(可选,搜索服务不可用)。
|
`Eiblog ` 默认会连接 `hostname` 为 `mongodb` 和 `elasticsearch` 的地址,因此你需要将信息填入 `/etc/hosts` 下。假如你搭建的 `mongodb` 地址为 `127.0.0.1:27017`,`elasticsearch` 地址为 `192.168.99.100:9200`,如:
|
||||||
|
|
||||||
`Eiblog`默认会连接`hostname`为`mongodb`和`elasticsearch`,因此你需要将信息填入`/etc/hosts`下。假如你搭建的`mongodb`地址为`127.0.0.1:27017`,`elasticsearch`地址为`192.168.99.100:9200`,如:
|
|
||||||
``` sh
|
``` sh
|
||||||
$ sudo vi /etc/hosts
|
$ sudo vi /etc/hosts
|
||||||
|
|
||||||
@@ -31,100 +28,108 @@ $ sudo vi /etc/hosts
|
|||||||
192.168.99.100 elasticsearch
|
192.168.99.100 elasticsearch
|
||||||
```
|
```
|
||||||
|
|
||||||
|
下面先看两个服务的搭建。
|
||||||
|
|
||||||
#### MongoDB 搭建
|
#### MongoDB 搭建
|
||||||
1、`MongoDB`搭建,Mac 可通过`brew install mongo`进行安装,其它平台请查询资料。
|
|
||||||
|
`MongoDB` 搭建,Mac 可通过 `brew install mongo` 进行安装,其它平台请查询资料。
|
||||||
#### Elasticsearch 搭建
|
#### Elasticsearch 搭建
|
||||||
2、`Elasticsearch`搭建,它的搭建要些许复杂。博主尚未接触如何直接安装,因此建议通过`docker`搭建。需要注意的是 es 自带的分析器对中文分词是不友好的,这里采用了`elasticsearch-analysis-ik`分词器。如果你想了解更多[Github](https://github.com/medcl/elasticsearch-analysis-ik)或则如何实现[博客站内搜索](https://imququ.com/post/elasticsearch.html)。
|
`Elasticsearch `搭建,它的搭建要些许复杂。建议通过 `docker` 搭建。需要注意的是 es 自带的分析器对中文分词是不友好的,这里采用了 `elasticsearch-analysis-ik` 分词器。如果你想了解更多 [Github](https://github.com/medcl/elasticsearch-analysis-ik) 或则如何实现 [博客站内搜索](https://imququ.com/post/elasticsearch.html)。
|
||||||
|
|
||||||
* pull 镜像`docker pull elasticsearch:2.4.1`,必需使用该版本。
|
1. pull 镜像 `docker pull elasticsearch:2.4.1`。
|
||||||
* 添加环境变量`ES_JAVA_OPTS: "-Xms512m -Xmx512m"`,除非你想让你的服务器爆掉。
|
|
||||||
* 映射相关目录:
|
|
||||||
|
|
||||||
```
|
2. 添加环境变量 `ES_JAVA_OPTS: "-Xms512m -Xmx512m"`,除非你想让你的服务器爆掉。
|
||||||
conf/es/config:/usr/share/elasticsearch/config
|
|
||||||
conf/es/plugins:/usr/share/elasticsearch/plugins
|
|
||||||
conf/es/data:/usr/share/elasticsearch/data
|
|
||||||
conf/es/logs:/usr/share/elasticsearch/logs
|
|
||||||
```
|
|
||||||
请将这四个目录映射至`eiblog`下的`conf`目录。如果你想查看更多,请查看`docker-compose.yml`文件。
|
|
||||||
|
|
||||||
总结一下,`docker`运行 es 的命令为:
|
3. 映射相关目录:
|
||||||
|
|
||||||
|
```
|
||||||
|
$PWD/conf/es/config:/usr/share/elasticsearch/config
|
||||||
|
$PWD/conf/es/plugins:/usr/share/elasticsearch/plugins
|
||||||
|
```
|
||||||
|
|
||||||
|
博主已经准备好了必要的 es 配置文件,请将这四个目录映射至 `eiblog` 下的 `conf` 目录。如果你想查看更多,请查看 `docker-compose.yml` 文件。
|
||||||
|
|
||||||
|
总结一下,`docker` 运行 es 的命令为:
|
||||||
``` sh
|
``` sh
|
||||||
$ docker run -d --name eisearch \
|
$ docker run -d --name eisearch \
|
||||||
-p 9200:9200 \
|
-p 9200:9200 \
|
||||||
-e ES_JAVA_OPTS: "-Xms512m -Xmx512m" \
|
-e ES_JAVA_OPTS="-Xms512m -Xmx512m" \
|
||||||
-v conf/es/config:/usr/share/elasticsearch/config \
|
-v $PWD/conf/es/config:/usr/share/elasticsearch/config \
|
||||||
-v conf/es/plugins:/usr/share/elasticsearch/plugins \
|
-v $PWD/conf/es/plugins:/usr/share/elasticsearch/plugins \
|
||||||
-v conf/es/data:/usr/share/elasticsearch/data \
|
|
||||||
-v conf/es/logs:/usr/share/elasticsearch/logs \
|
|
||||||
elasticsearch:2.4.1
|
elasticsearch:2.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
之后执行`./eiblog`,咱们的`eiblog`就可以运行起来了。
|
之后执行 `./eiblog`,咱们的 `eiblog` 就可以运行起来了。
|
||||||
|
|
||||||
通过`127.0.0.1:9000`可以进入博客首页,`127.0.0.1:9000/admin/login`进入后台登陆,账号密码为`eiblog/conf/app.yml`下的`username`和`password`。也就是初始账号密码`deepz`、`deepzz`。
|
通过 `127.0.0.1:9000` 可以进入博客首页,`127.0.0.1:9000/admin/login` 进入后台登陆,账号密码为 `eiblog/conf/app.yml` 下的 `username` 和 `password`。初始账号密码 `deepz`、`deepzz`。
|
||||||
|
|
||||||
> `注意`,因为配置`conf/app.yml`均是博主自用配置。有些操作可能(如评论)会评论到我的博客,还请尽量避免,谢谢。
|
> `注意`,因为配置 `conf/app.yml` 均是博主自用配置。有些操作可能(如评论)会评论到我的博客,还请尽量避免,谢谢。
|
||||||
|
|
||||||
### 准备部署
|
### 准备部署
|
||||||
如果你在感受了该博客的魅力了之后,仍然坚持想要搭建它。那么,恭喜你,获得的一款不想再更换的博客系统。下面,我们跟随步骤对部署流程进一步说明。
|
如果你在感受了该博客的魅力了之后,仍然坚持想要搭建它。那么,恭喜你,获得的一款不想再更换的博客系统。下面,我们跟随步骤对部署流程进一步说明。
|
||||||
|
|
||||||
这里只提供`Docker`的相关部署说明。你如果需要其它方式部署,请参考该方式。
|
这里只提供 `Docker` 的相关部署说明。你如果需要其它方式部署,请参考该方式。
|
||||||
|
|
||||||
#### 前提准备
|
#### 前提准备
|
||||||
这里需要准备一些必要的东西,如果你已准备好。请跳过。
|
这里需要准备一些必要的东西,如果你已准备好。请跳过。
|
||||||
|
|
||||||
* `一台服务器`。
|
* `一台服务器`。
|
||||||
* `一个域名`,国内服务器需备案。
|
* `一个域名`,国内服务器需备案。
|
||||||
* `有效的证书`。一般使用免费的就可以。如:`Let‘s Encrypt`,另外`qcloud`、`七牛`也提供了免费证书的申请,均是全球可信。
|
* `有效的证书`。通过开启 autocert 可自动申请更新证书。也可去七牛、qcloud 申请一年有效证书。
|
||||||
* `七牛CDN`。博客只设计接入了七牛cdn,相信该CDN服务商不会让你失望。
|
* `七牛CDN`。博客只设计接入了 七牛cdn,相信该 CDN 服务商不会让你失望。
|
||||||
* `Disqus`。作为博客评论系统,你得有翻墙的能力注册到该账号,具体配置我想又可以写一片博客了。简单说需要`shorname`和`public key`。
|
* `Disqus`。作为博客评论系统,你得有翻墙的能力注册到该账号,具体配置我想又可以写一片博客了。简单说需要 `shorname` 和 `public key`。
|
||||||
* `Google Analystic`。数据统计分析工具。
|
* `Google Analystic`。数据统计分析工具。
|
||||||
* `Superfeedr`。加速 RSS 订阅。
|
* `Superfeedr`。加速 RSS 订阅。
|
||||||
* `Twitter`。希望你能够有一个twitter账号。
|
* `Twitter`。希望你能够有一个 twitter 账号。
|
||||||
|
|
||||||
是不是这么多要求,很费解。其实当初该博客系统只是为个人而设计的,是自己心中想要的那一款。博主些这篇文章不是想要多少人来用该博客,而是希望对那些追求至极的朋友说:你需要这款博客系统。
|
是不是这么多要求,很费解。其实当初该博客系统只是为个人而设计的,是自己心中想要的那一款。博主些这篇文章不是想要多少人来用该博客,而是希望对那些追求至极的朋友说:你需要这款博客系统。
|
||||||
#### 文件准备
|
#### 文件准备
|
||||||
尽管大多数文件已经准备好。但有些默认的文件需要特别指出来,需要你在 CDN 上写特殊的路径。
|
博主是一个有强迫症的人,一些文件的路径我使用了固定的路径,请大家见谅。假如你的 cdn 域名为 `st.example.com`,你需要确定这些文件已经在你的 cdn 中,它们路径分别是:
|
||||||
|
|
||||||
假如你的 CDN 域名为`st.example.com`,那么请确保你的 CDN 中已经上传以下文件:
|
| 文件 | 地址 | 描述 |
|
||||||
|
| ------------------ | ---------------------------------------- | ---------------------------------------- |
|
||||||
|
| favicon.ico | st.example.com/static/img/favicon.ico | cdn 中的文件名为 `static/img/favicon.ico`。你也可以复制 favicon.ico 到 static 文件夹下,通过 example.com/favicon.ico 也是能够访问到。docker 用户可能需要重新打包镜像。 |
|
||||||
|
| bg04.jpg | st.example.com/static/img/bg04.jpg | 首页左侧的大背景图,需要更名请到 views/st_blog.css 修改。 |
|
||||||
|
| avatar.jpg | st.example.com/static/img/avatar.jpg | 头像 |
|
||||||
|
| blank.gif | st.example.com/static/img/blank.gif | 空白图片,[下载](https://st.deepzz.com/static/img/blank.gif) |
|
||||||
|
| default_avatar.png | st.example.com/static/img/default_avatar.png | disqus 默认图片,[下载](https://st.deepzz.com/static/img/default_avatar.png) |
|
||||||
|
| disqus.js | st.example.com/static/js/disqus_xxx.js | disqus 文件,你可以通过 https://short_name.disqus.com/embed.js 下载你的专属文件,并上传到七牛。更新配置文件 app.yml。 |
|
||||||
|
|
||||||
* `favicon.ico`,默认为`st.example.com/static/img/favicon.ico`。故你在 CDN 中的文件名为`static/img/favicon.ico`,以下如是。
|
> 注意,cdn 提到的文件下载,请复制链接进行下载,因为博主使用了防盗链功能,还有:
|
||||||
* `bg04.jpg`,左侧背景图片,`500*1200`左右,CDN 中文件名:`static/img/bg04.jpg`。如需更改,请在`eiblog/view/st_blog.css`中替换该名称。
|
1、每次修改 app.yml 文件(如:更换 cdn 域名或更新头像),如果你不知道是否应该提高 staticversion 一个版本,那么最好提高一个 +1。
|
||||||
* `avatar.jpg`,左侧头像,`160*160~256*256`之间,CDN 文件名:`static/img/avatar.jpg`。另外你需要将该图片 `Base64` 编码后替换掉`eiblog/views/st_blog.css`中合适位置的图片。
|
2、每次手动修改 views 内的以 `st_` 开头的文件,请将 `app.yml` 中的 staticversion 提高一个版本。
|
||||||
* `blank.gif`,CDN 文件名:`static/img/blank.gif`。该图片请从[这里](https://st.deepzz.com/static/img/blank.gif)下载并上传至你的 CDN。
|
|
||||||
* `default_avatar.png`,Disqus 默认头像,CDN 文件名:`static/img/default_avatar.png`,请从[这里](https://st.deepzz.com/static/img/default_avatar.png)下载并上传至你的 CDN。
|
|
||||||
* `disqus.js`,通过 https://short_name.disqus.com/embed.js 下载,替换掉 short_name。CDN 文件名格式是:`static/js/disqus_xxx.js`。在我写这篇文章是使用的是:`static/js/disqus_a9d3fd.js`。另外修改`eiblog/views/st_blog.js`中的`disqus_a9d3fd.js`为你新文件的名称。
|
|
||||||
|
|
||||||
> 注意:每次修改 views 内的以 `st_` 开头的文件,请将 `app.yml` 中的 staticversion 提高一个版本。
|
|
||||||
|
|
||||||
#### 配置说明
|
#### 配置说明
|
||||||
走到这里,我相信只走到`60%`的路程。放弃还来得及。
|
走到这里,我相信只走到 `60%` 的路程。放弃还来得及。
|
||||||
|
|
||||||
这里会对`eiblog/conf`下的所有文件做说明,希望你做好准备。
|
这里会对 `eiblog/conf` 下的所有文件做说明,希望你做好准备。
|
||||||
```
|
```
|
||||||
├── app.yml # 博客配置文件
|
├── app.yml # 博客配置文件
|
||||||
├── blackip.yml # 博客ip黑名单
|
├── blackip.yml # 博客 ip 黑名单
|
||||||
├── es # elasticsearch配置
|
├── es # elasticsearch 配置
|
||||||
│ ├── config # 配置文件
|
│ ├── config # 配置文件
|
||||||
│ │ ├── analysis # 同义词
|
│ │ ├── analysis # 同义词
|
||||||
│ │ ├── elasticsearch.yml # 具体配置
|
│ │ ├── elasticsearch.yml # 具体配置
|
||||||
│ │ ├── logging.yml # 日志配置
|
│ │ ├── logging.yml # 日志配置
|
||||||
│ │ └── scripts # 脚本文件夹
|
│ │ └── scripts # 脚本文件夹
|
||||||
│ └── plugins # 插件文件夹
|
│ └── plugins # 插件文件夹
|
||||||
│ └── ik1.10.1 # ik分词器
|
│ └── ik1.10.1 # ik 分词器
|
||||||
├── nginx # nginx配置
|
├── nginx # nginx 配置
|
||||||
│ ├── domain # 域名配置,nginx会读区改文件夹下的.conf文件
|
│ ├── domain # 域名配置,nginx 会读区改文件夹下的 .conf 文件
|
||||||
│ │ └── deepzz.conf
|
│ │ └── eiblog.conf
|
||||||
│ ├── ip.blacklist # nginx ip黑名单
|
│ ├── ip.blacklist # nginx ip黑名单
|
||||||
│ └── nginx.conf # nginx配置,请替换原有配置
|
│ └── nginx.conf # nginx 配置,请替换 nginx 原有配置
|
||||||
├── scts # ct文件
|
├── scts # ct 透明
|
||||||
│ ├── aviator.sct
|
│ ├── ecc
|
||||||
│ └── digicert.sct
|
│ │ ├── aviator.sct
|
||||||
├── ssl # 证书文件,具体请看deepzz.conf
|
│ │ └── digicert.sct
|
||||||
|
│ └── rsa
|
||||||
|
│ ├── aviator.sct
|
||||||
|
│ └── digicert.sct
|
||||||
|
├── ssl # 证书相关文件,可参考 eiblog.conf 生成
|
||||||
│ ├── dhparams.pem
|
│ ├── dhparams.pem
|
||||||
│ ├── domain.key
|
│ ├── domain.rsa.key
|
||||||
│ ├── domain.pem
|
│ ├── domain.rsa.pem
|
||||||
│ ├── full_chained.pem
|
│ ├── full_chained.pem
|
||||||
│ └── session_ticket.key
|
│ └── session_ticket.key
|
||||||
└── tpl # 模版文件
|
└── tpl # 模版文件
|
||||||
@@ -133,55 +138,27 @@ $ docker run -d --name eisearch \
|
|||||||
├── opensearchTpl.xml
|
├── opensearchTpl.xml
|
||||||
├── robotsTpl.xml
|
├── robotsTpl.xml
|
||||||
└── sitemapTpl.xml
|
└── sitemapTpl.xml
|
||||||
|
|
||||||
```
|
```
|
||||||
1、app.yml,整个程序的配置文件,里面已经列出了所有配置项的说明,这里不再阐述。
|
| 名称 | 描述 |
|
||||||
2、blackip.yml,如果没有使用`Nginx`,博客内置`ip`过滤系统。
|
| ----------- | ---------------------------------------- |
|
||||||
3、`es`全名`elasticsearch`,非常强大的分布式搜索引擎,`github`用的就是它。里面的配置基本不用修改,但`es/analysis/synonym.txt`是同义词,你可以照着已有的随意增加。
|
| app.yml | 整个程序的配置文件,里面已经列出了所有配置项的说明,这里不再阐述。 |
|
||||||
```
|
| blackip.yml | 如果没有使用 `Nginx`,博客内置 `ip` 过滤系统。 |
|
||||||
├── es
|
| es | elasticsearch,非常强大的分布式搜索引擎,`github` 用的就是它。里面的配置基本不用修改,但 `es/analysis/synonym.txt` 是同义词,你可以照着已有的随意增加。scripts 是 es 的脚本文件夹 |
|
||||||
│ ├── config
|
| nginx | 系统采用 `nginx` 作为代理(相信博客系统也不会独占一台服务器~)。请使用 `nginx.conf` 替换原 `nginx` 的配置。博客系统的配置文件是 `domain/eiblog.conf`,或则重命名(只要是满足`*.conf`)。`eiblog.conf`文件里面学问是最多的。或许你想一一弄懂,或许…。注意本配置需要更新 nginx 到最新版,openssl 更新到1.0.2j,具体请到 Jerry Qu 的 [本博客 Nginx 配置之完整篇](https://imququ.com/post/my-nginx-conf.html) 查看,了解详情。 |
|
||||||
│ │ ├── analysis
|
| scts | 存放 ct 文件。 |
|
||||||
│ │ │ └── synonym.txt #同义词配置
|
| ssl | 这里存放了所有证书相关的内容。 |
|
||||||
│ │ ├── elasticsearch.yml #分词器配置
|
| tpl | 模版相关,不用修改。 |
|
||||||
│ │ ├── logging.yml #日志配置
|
|
||||||
│ │ └── scripts #脚本
|
|
||||||
│ └── plugins #中文分词插件
|
|
||||||
│ └── ik1.10.0
|
|
||||||
│
|
|
||||||
```
|
|
||||||
|
|
||||||
> `注意`,scripts文件夹虽然是空的,但必需存在,不然elasticsearch报错。
|
|
||||||
|
|
||||||
4、`nginx`,系统采用`nginx`作为代理(相信博客系统也不会独占一台服务器~)。请使用`nginx.conf`替换原`nginx`的配置。博客系统的配置文件是`domain/deepzz.conf`,或则重命名(只要是满足`*.conf`)。`deepzz.conf`文件里面学问是最多的。或许你想一一弄懂,或许…。
|
|
||||||
|
|
||||||
> 注意本配置需要更新nginx到最新版,openssl更新到1.0.2j,具体请到 Jerry Qu 的[本博客 Nginx 配置之完整篇](https://imququ.com/post/my-nginx-conf.html)查看,了解详情。
|
|
||||||
|
|
||||||
5、`scts`,存放 ct 文件。
|
|
||||||
|
|
||||||
6、`ssl`,这里存放了所有证书相关的内容。
|
|
||||||
```
|
|
||||||
├── dhparams.pem #参见eiblog/conf/nginx/domain/deepzz.conf
|
|
||||||
├── domain.key #证书私钥,一般颁发者处下载
|
|
||||||
├── domain.pem #证书链,一般从证书颁发者那可以直接下载到
|
|
||||||
├── full_chained.pem #参见eiblog/conf/nginx/domain/deepzz.conf
|
|
||||||
└── session_ticket.key #参见eiblog/conf/nginx/domain/deepzz.conf
|
|
||||||
```
|
|
||||||
|
|
||||||
7、`tpl`模版相关,不用修改。
|
|
||||||
|
|
||||||
### 开始部署
|
### 开始部署
|
||||||
|
|
||||||
#### docker
|
#### docker
|
||||||
请确定你已经完成了上面所说的所有步骤,在本地已经测试成功。服务器上`MognoDB`和`Elasticsearch`已经安装并已经运行成功。
|
请确定你已经完成了上面所说的所有步骤,在本地已经测试成功。服务器上 `MognoDB` 和`Elasticsearch` 已经安装并已经运行成功。
|
||||||
|
|
||||||
首先,请将本地测试好的`conf`,`static`,`views`文件夹上传至服务器,建议存储到服务器`/data/eiblog`下。
|
首先,请将本地测试好的 `conf` 文件夹上传至服务器,建议存储到服务器 `/data/eiblog` 下。
|
||||||
``` sh
|
``` sh
|
||||||
$ tree /data/eiblog -L 1
|
$ tree /data/eiblog -L 1
|
||||||
|
|
||||||
├── conf
|
├── conf
|
||||||
├── static
|
|
||||||
├── views
|
|
||||||
```
|
```
|
||||||
|
|
||||||
然后,将镜像 PULL 到服务器本地。
|
然后,将镜像 PULL 到服务器本地。
|
||||||
@@ -190,7 +167,7 @@ $ tree /data/eiblog -L 1
|
|||||||
$ docker pull registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog
|
$ docker pull registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog
|
||||||
```
|
```
|
||||||
|
|
||||||
最后,执行`docker run`命令,希望你能成功。
|
最后,执行 `docker run` 命令,希望你能成功。
|
||||||
``` sh
|
``` sh
|
||||||
$ docker run -d --name eiblog --restart=always \
|
$ docker run -d --name eiblog --restart=always \
|
||||||
--add-host disqus.com:23.235.33.134 \
|
--add-host disqus.com:23.235.33.134 \
|
||||||
@@ -200,27 +177,21 @@ $ docker run -d --name eiblog --restart=always \
|
|||||||
-e GODEBUG=netdns=cgo \
|
-e GODEBUG=netdns=cgo \
|
||||||
-v /data/eiblog/logdata:/eiblog/logdata \
|
-v /data/eiblog/logdata:/eiblog/logdata \
|
||||||
-v /data/eiblog/conf:/eiblog/conf \
|
-v /data/eiblog/conf:/eiblog/conf \
|
||||||
-v /data/eiblog/static:/eiblog/static \
|
|
||||||
-v /data/eiblog/views:/eiblog/views \
|
|
||||||
registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog
|
registry.cn-hangzhou.aliyuncs.com/deepzz/eiblog
|
||||||
```
|
```
|
||||||
这里默认`MongDB`和`Elasticsearch`均为`docker`部署,且名称为`eidb`,`eisearch`。
|
这里默认 `MongDB` 和 `Elasticsearch` 均为 `docker` 部署,且名称为`eidb`,`eisearch`。
|
||||||
|
|
||||||
#### nginx + docker
|
#### nginx + docker
|
||||||
通过`Nginx+docker`部署,是博主推荐的方式。这里采用`Docker Compose`管理我们整个博客系统。
|
通过 `Nginx+docker` 部署,是博主推荐的方式。这里采用 `Docker Compose` 管理我们整个博客系统。
|
||||||
|
|
||||||
请确认你已经成功安装好`Nginx`、`docker`、`docker-compose`。Nginx 请一定参照 Jerry Qu 的[Nginx 配置完整篇](https://imququ.com/post/my-nginx-conf.html)。
|
请确认你已经成功安装好 `Nginx`、`docker`、`docker-compose`。Nginx 请一定参照 Jerry Qu 的[Nginx 配置完整篇](https://imququ.com/post/my-nginx-conf.html)。
|
||||||
|
|
||||||
首先,请将本地测试好的`conf`,`static`,`views`,`docker-compose.yml`文件夹和文件上传至服务器。前三个文件夹建议存储到服务器`/data/eiblog`下,`docker-compose.yml`存放在你使用方便的地方。
|
首先,请将本地测试好的 `conf`,`docker-compose.yml` 文件夹和文件上传至服务器。`conf` 建议存储到服务器 `/data/eiblog` 下,`docker-compose.yml` 存放在你使用方便的地方。
|
||||||
|
|
||||||
> 注意检查`conf/es/config/scripts`空文件夹是否存在
|
|
||||||
|
|
||||||
``` sh
|
``` sh
|
||||||
$ tree /data/eiblog -L 1
|
$ tree /data/eiblog -L 1
|
||||||
|
|
||||||
├── conf
|
├── conf
|
||||||
├── static
|
|
||||||
├── views
|
|
||||||
|
|
||||||
$ ls ~/
|
$ ls ~/
|
||||||
|
|
||||||
@@ -234,5 +205,3 @@ $ docker-compose up -d
|
|||||||
```
|
```
|
||||||
|
|
||||||
等待些许时间,成功运行。
|
等待些许时间,成功运行。
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/eiblog/eiblog/setting"
|
|
||||||
"github.com/eiblog/utils/logd"
|
"github.com/eiblog/utils/logd"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,11 +25,13 @@ const (
|
|||||||
|
|
||||||
var es *ElasticService
|
var es *ElasticService
|
||||||
|
|
||||||
|
// 初始化 Elasticsearch 服务器
|
||||||
func init() {
|
func init() {
|
||||||
es = &ElasticService{url: setting.Conf.SearchURL, c: new(http.Client)}
|
es = &ElasticService{url: "http://elasticsearch:9200", c: new(http.Client)}
|
||||||
initIndex()
|
initIndex()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建索引
|
||||||
func initIndex() {
|
func initIndex() {
|
||||||
mappings := fmt.Sprintf(`{"mappings":{"%s":{"properties":{"content":{"analyzer":"ik_syno","search_analyzer":"ik_syno","term_vector":"with_positions_offsets","type":"string"},"date":{"index":"not_analyzed","type":"date"},"slug":{"type":"string"},"tag":{"index":"not_analyzed","type":"string"},"title":{"analyzer":"ik_syno","search_analyzer":"ik_syno","term_vector":"with_positions_offsets","type":"string"}}}}}`, TYPE)
|
mappings := fmt.Sprintf(`{"mappings":{"%s":{"properties":{"content":{"analyzer":"ik_syno","search_analyzer":"ik_syno","term_vector":"with_positions_offsets","type":"string"},"date":{"index":"not_analyzed","type":"date"},"slug":{"type":"string"},"tag":{"index":"not_analyzed","type":"string"},"title":{"analyzer":"ik_syno","search_analyzer":"ik_syno","term_vector":"with_positions_offsets","type":"string"}}}}}`, TYPE)
|
||||||
err := CreateIndexAndMappings(INDEX, TYPE, []byte(mappings))
|
err := CreateIndexAndMappings(INDEX, TYPE, []byte(mappings))
|
||||||
@@ -39,6 +40,7 @@ func initIndex() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查询
|
||||||
func Elasticsearch(qStr string, size, from int) *ESSearchResult {
|
func Elasticsearch(qStr string, size, from int) *ESSearchResult {
|
||||||
// 分析查询字符串
|
// 分析查询字符串
|
||||||
reg := regexp.MustCompile(`(tag|slug|date):`)
|
reg := regexp.MustCompile(`(tag|slug|date):`)
|
||||||
@@ -96,6 +98,7 @@ func Elasticsearch(qStr string, size, from int) *ESSearchResult {
|
|||||||
return docs
|
return docs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加或更新索引
|
||||||
func ElasticIndex(artc *Article) error {
|
func ElasticIndex(artc *Article) error {
|
||||||
img := PickFirstImage(artc.Content)
|
img := PickFirstImage(artc.Content)
|
||||||
mapping := map[string]interface{}{
|
mapping := map[string]interface{}{
|
||||||
@@ -110,6 +113,7 @@ func ElasticIndex(artc *Article) error {
|
|||||||
return IndexOrUpdateDocument(INDEX, TYPE, artc.ID, b)
|
return IndexOrUpdateDocument(INDEX, TYPE, artc.ID, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 删除索引
|
||||||
func ElasticDelIndex(ids []int32) error {
|
func ElasticDelIndex(ids []int32) error {
|
||||||
var target []string
|
var target []string
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
@@ -128,10 +132,12 @@ type IndicesCreateResult struct {
|
|||||||
Acknowledged bool `json:"acknowledged"`
|
Acknowledged bool `json:"acknowledged"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 返回 url
|
||||||
func (s *ElasticService) ParseURL(format string, params ...interface{}) string {
|
func (s *ElasticService) ParseURL(format string, params ...interface{}) string {
|
||||||
return fmt.Sprintf(s.url+format, params...)
|
return fmt.Sprintf(s.url+format, params...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Elastic 相关操作请求
|
||||||
func (s *ElasticService) Do(req *http.Request) (interface{}, error) {
|
func (s *ElasticService) Do(req *http.Request) (interface{}, error) {
|
||||||
resp, err := s.c.Do(req)
|
resp, err := s.c.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -153,11 +159,8 @@ func (s *ElasticService) Do(req *http.Request) (interface{}, error) {
|
|||||||
return b, nil
|
return b, nil
|
||||||
case "HEAD":
|
case "HEAD":
|
||||||
return resp.StatusCode, nil
|
return resp.StatusCode, nil
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, errors.New("unknown methods")
|
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, errors.New("unknown methods")
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateIndexAndMappings(index, typ string, mappings []byte) (err error) {
|
func CreateIndexAndMappings(index, typ string, mappings []byte) (err error) {
|
||||||
@@ -188,6 +191,7 @@ func CreateIndexAndMappings(index, typ string, mappings []byte) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建或更新索引
|
||||||
func IndexOrUpdateDocument(index, typ string, id int32, doc []byte) (err error) {
|
func IndexOrUpdateDocument(index, typ string, id int32, doc []byte) (err error) {
|
||||||
req, err := http.NewRequest("PUT", es.ParseURL("/%s/%s/%d", index, typ, id), bytes.NewReader(doc))
|
req, err := http.NewRequest("PUT", es.ParseURL("/%s/%s/%d", index, typ, id), bytes.NewReader(doc))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -214,6 +218,7 @@ type ESDeleteResult struct {
|
|||||||
} `json:"iterms"`
|
} `json:"iterms"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 删除文档
|
||||||
func DeleteDocument(index, typ string, ids []string) error {
|
func DeleteDocument(index, typ string, ids []string) error {
|
||||||
var buff bytes.Buffer
|
var buff bytes.Buffer
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
@@ -248,6 +253,7 @@ func DeleteDocument(index, typ string, ids []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查询结果
|
||||||
type ESSearchResult struct {
|
type ESSearchResult struct {
|
||||||
Took float32 `json:"took"`
|
Took float32 `json:"took"`
|
||||||
Hits struct {
|
Hits struct {
|
||||||
@@ -269,6 +275,7 @@ type ESSearchResult struct {
|
|||||||
} `json:"hits"`
|
} `json:"hits"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DSL 语句查询文档
|
||||||
func IndexQueryDSL(index, typ string, size, from int, dsl []byte) (*ESSearchResult, error) {
|
func IndexQueryDSL(index, typ string, size, from int, dsl []byte) (*ESSearchResult, error) {
|
||||||
req, err := http.NewRequest("POST", es.ParseURL("/%s/%s/_search?size=%d&from=%d", index, typ, size, from), bytes.NewReader(dsl))
|
req, err := http.NewRequest("POST", es.ParseURL("/%s/%s/_search?size=%d&from=%d", index, typ, size, from), bytes.NewReader(dsl))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
144
front.go
144
front.go
@@ -20,13 +20,46 @@ import (
|
|||||||
func Filter() gin.HandlerFunc {
|
func Filter() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
// 过滤黑名单
|
// 过滤黑名单
|
||||||
BlackFilter(c)
|
if BlackFilter(c) {
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 重定向
|
||||||
|
if Redirect(c) {
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
// 用户cookie,用于统计
|
// 用户cookie,用于统计
|
||||||
UserCookie(c)
|
UserCookie(c)
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 黑名单过滤
|
||||||
|
func BlackFilter(c *gin.Context) bool {
|
||||||
|
ip := c.ClientIP()
|
||||||
|
if setting.BlackIP[ip] {
|
||||||
|
c.String(http.StatusForbidden, "Your IP is blacklisted.")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重定向
|
||||||
|
func Redirect(c *gin.Context) bool {
|
||||||
|
if setting.Conf.Mode.EnableHttps && c.Request.ProtoMajor == 1 {
|
||||||
|
var port string
|
||||||
|
if strings.Contains(c.Request.Host, ":") {
|
||||||
|
port = fmt.Sprintf(":%d", setting.Conf.Mode.HttpsPort)
|
||||||
|
}
|
||||||
|
c.Redirect(http.StatusMovedPermanently, "https://"+setting.Conf.Mode.Domain+port+c.Request.RequestURI)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// 用户识别
|
// 用户识别
|
||||||
func UserCookie(c *gin.Context) {
|
func UserCookie(c *gin.Context) {
|
||||||
cookie, err := c.Cookie("u")
|
cookie, err := c.Cookie("u")
|
||||||
@@ -35,15 +68,6 @@ func UserCookie(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 黑名单过滤
|
|
||||||
func BlackFilter(c *gin.Context) {
|
|
||||||
ip := c.ClientIP()
|
|
||||||
if setting.BlackIP[ip] {
|
|
||||||
c.Abort()
|
|
||||||
c.String(http.StatusForbidden, "Your IP is blacklisted.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析静态文件版本
|
// 解析静态文件版本
|
||||||
func StaticVersion(c *gin.Context) (version int) {
|
func StaticVersion(c *gin.Context) (version int) {
|
||||||
cookie, err := c.Request.Cookie("v")
|
cookie, err := c.Request.Cookie("v")
|
||||||
@@ -55,7 +79,6 @@ func StaticVersion(c *gin.Context) (version int) {
|
|||||||
|
|
||||||
func GetBase() gin.H {
|
func GetBase() gin.H {
|
||||||
return gin.H{
|
return gin.H{
|
||||||
"Favicon": setting.Conf.Favicon,
|
|
||||||
"BlogName": Ei.BlogName,
|
"BlogName": Ei.BlogName,
|
||||||
"SubTitle": Ei.SubTitle,
|
"SubTitle": Ei.SubTitle,
|
||||||
"Twitter": setting.Conf.Twitter,
|
"Twitter": setting.Conf.Twitter,
|
||||||
@@ -63,10 +86,12 @@ func GetBase() gin.H {
|
|||||||
"BTitle": Ei.BTitle,
|
"BTitle": Ei.BTitle,
|
||||||
"BeiAn": Ei.BeiAn,
|
"BeiAn": Ei.BeiAn,
|
||||||
"Domain": setting.Conf.Mode.Domain,
|
"Domain": setting.Conf.Mode.Domain,
|
||||||
"Kodo": setting.Conf.Kodo,
|
"Qiniu": setting.Conf.Qiniu,
|
||||||
|
"Disqus": setting.Conf.Disqus,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// not found
|
||||||
func HandleNotFound(c *gin.Context) {
|
func HandleNotFound(c *gin.Context) {
|
||||||
h := GetBase()
|
h := GetBase()
|
||||||
h["Version"] = StaticVersion(c)
|
h["Version"] = StaticVersion(c)
|
||||||
@@ -77,6 +102,7 @@ func HandleNotFound(c *gin.Context) {
|
|||||||
RenderHTMLFront(c, "notfound", h)
|
RenderHTMLFront(c, "notfound", h)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 首页
|
||||||
func HandleHomePage(c *gin.Context) {
|
func HandleHomePage(c *gin.Context) {
|
||||||
h := GetBase()
|
h := GetBase()
|
||||||
h["Version"] = StaticVersion(c)
|
h["Version"] = StaticVersion(c)
|
||||||
@@ -88,11 +114,12 @@ func HandleHomePage(c *gin.Context) {
|
|||||||
if err != nil || pn < 1 {
|
if err != nil || pn < 1 {
|
||||||
pn = 1
|
pn = 1
|
||||||
}
|
}
|
||||||
h["Prev"], h["Next"], h["List"] = PageList(pn, setting.Conf.PageNum)
|
h["Prev"], h["Next"], h["List"] = PageList(pn, setting.Conf.General.PageNum)
|
||||||
c.Status(http.StatusOK)
|
c.Status(http.StatusOK)
|
||||||
RenderHTMLFront(c, "home", h)
|
RenderHTMLFront(c, "home", h)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 专题页
|
||||||
func HandleSeriesPage(c *gin.Context) {
|
func HandleSeriesPage(c *gin.Context) {
|
||||||
h := GetBase()
|
h := GetBase()
|
||||||
h["Version"] = StaticVersion(c)
|
h["Version"] = StaticVersion(c)
|
||||||
@@ -105,6 +132,7 @@ func HandleSeriesPage(c *gin.Context) {
|
|||||||
RenderHTMLFront(c, "series", h)
|
RenderHTMLFront(c, "series", h)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 归档页
|
||||||
func HandleArchivesPage(c *gin.Context) {
|
func HandleArchivesPage(c *gin.Context) {
|
||||||
h := GetBase()
|
h := GetBase()
|
||||||
h["Version"] = StaticVersion(c)
|
h["Version"] = StaticVersion(c)
|
||||||
@@ -117,6 +145,7 @@ func HandleArchivesPage(c *gin.Context) {
|
|||||||
RenderHTMLFront(c, "archives", h)
|
RenderHTMLFront(c, "archives", h)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 文章
|
||||||
func HandleArticlePage(c *gin.Context) {
|
func HandleArticlePage(c *gin.Context) {
|
||||||
path := c.Param("slug")
|
path := c.Param("slug")
|
||||||
if !strings.HasSuffix(path, ".html") || Ei.MapArticles[path[:len(path)-5]] == nil {
|
if !strings.HasSuffix(path, ".html") || Ei.MapArticles[path[:len(path)-5]] == nil {
|
||||||
@@ -154,6 +183,7 @@ func HandleArticlePage(c *gin.Context) {
|
|||||||
RenderHTMLFront(c, name, h)
|
RenderHTMLFront(c, name, h)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 搜索页
|
||||||
func HandleSearchPage(c *gin.Context) {
|
func HandleSearchPage(c *gin.Context) {
|
||||||
h := GetBase()
|
h := GetBase()
|
||||||
h["Version"] = StaticVersion(c)
|
h["Version"] = StaticVersion(c)
|
||||||
@@ -171,7 +201,7 @@ func HandleSearchPage(c *gin.Context) {
|
|||||||
h["Word"] = q
|
h["Word"] = q
|
||||||
var result *ESSearchResult
|
var result *ESSearchResult
|
||||||
vals := c.Request.URL.Query()
|
vals := c.Request.URL.Query()
|
||||||
result = Elasticsearch(q, setting.Conf.PageNum, start-1)
|
result = Elasticsearch(q, setting.Conf.General.PageNum, start-1)
|
||||||
if result != nil {
|
if result != nil {
|
||||||
result.Took /= 1000
|
result.Took /= 1000
|
||||||
for i, v := range result.Hits.Hits {
|
for i, v := range result.Hits.Hits {
|
||||||
@@ -180,12 +210,12 @@ func HandleSearchPage(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
h["SearchResult"] = result
|
h["SearchResult"] = result
|
||||||
if start-setting.Conf.PageNum > 0 {
|
if start-setting.Conf.General.PageNum > 0 {
|
||||||
vals.Set("start", fmt.Sprint(start-setting.Conf.PageNum))
|
vals.Set("start", fmt.Sprint(start-setting.Conf.General.PageNum))
|
||||||
h["Prev"] = vals.Encode()
|
h["Prev"] = vals.Encode()
|
||||||
}
|
}
|
||||||
if result.Hits.Total >= start+setting.Conf.PageNum {
|
if result.Hits.Total >= start+setting.Conf.General.PageNum {
|
||||||
vals.Set("start", fmt.Sprint(start+setting.Conf.PageNum))
|
vals.Set("start", fmt.Sprint(start+setting.Conf.General.PageNum))
|
||||||
h["Next"] = vals.Encode()
|
h["Next"] = vals.Encode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,6 +226,7 @@ func HandleSearchPage(c *gin.Context) {
|
|||||||
RenderHTMLFront(c, "search", h)
|
RenderHTMLFront(c, "search", h)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 评论页
|
||||||
func HandleDisqusFrom(c *gin.Context) {
|
func HandleDisqusFrom(c *gin.Context) {
|
||||||
params := strings.Split(c.Param("slug"), "|")
|
params := strings.Split(c.Param("slug"), "|")
|
||||||
if len(params) != 4 || params[1] == "" {
|
if len(params) != 4 || params[1] == "" {
|
||||||
@@ -216,22 +247,36 @@ func HandleDisqusFrom(c *gin.Context) {
|
|||||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// feed
|
||||||
func HandleFeed(c *gin.Context) {
|
func HandleFeed(c *gin.Context) {
|
||||||
http.ServeFile(c.Writer, c.Request, "static/feed.xml")
|
http.ServeFile(c.Writer, c.Request, "static/feed.xml")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// opensearch
|
||||||
func HandleOpenSearch(c *gin.Context) {
|
func HandleOpenSearch(c *gin.Context) {
|
||||||
http.ServeFile(c.Writer, c.Request, "static/opensearch.xml")
|
http.ServeFile(c.Writer, c.Request, "static/opensearch.xml")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// robots
|
||||||
func HandleRobots(c *gin.Context) {
|
func HandleRobots(c *gin.Context) {
|
||||||
http.ServeFile(c.Writer, c.Request, "static/robots.txt")
|
http.ServeFile(c.Writer, c.Request, "static/robots.txt")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sitemap
|
||||||
func HandleSitemap(c *gin.Context) {
|
func HandleSitemap(c *gin.Context) {
|
||||||
http.ServeFile(c.Writer, c.Request, "static/sitemap.xml")
|
http.ServeFile(c.Writer, c.Request, "static/sitemap.xml")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cross domain
|
||||||
|
func HandleCrossDomain(c *gin.Context) {
|
||||||
|
http.ServeFile(c.Writer, c.Request, "static/crossdomain.xml")
|
||||||
|
}
|
||||||
|
|
||||||
|
// favicon
|
||||||
|
func HandleFavicon(c *gin.Context) {
|
||||||
|
http.ServeFile(c.Writer, c.Request, "static/favicon.ico")
|
||||||
|
}
|
||||||
|
|
||||||
// 服务端推送谷歌统计
|
// 服务端推送谷歌统计
|
||||||
func HandleBeacon(c *gin.Context) {
|
func HandleBeacon(c *gin.Context) {
|
||||||
ua := c.Request.UserAgent()
|
ua := c.Request.UserAgent()
|
||||||
@@ -246,7 +291,7 @@ func HandleBeacon(c *gin.Context) {
|
|||||||
vals.Set("dl", c.Request.Referer())
|
vals.Set("dl", c.Request.Referer())
|
||||||
vals.Set("uip", c.ClientIP())
|
vals.Set("uip", c.ClientIP())
|
||||||
go func() {
|
go func() {
|
||||||
req, err := http.NewRequest("POST", "https://www.google-analytics.com/collect", strings.NewReader(vals.Encode()))
|
req, err := http.NewRequest("POST", setting.Conf.Google.URL, strings.NewReader(vals.Encode()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logd.Error(err)
|
logd.Error(err)
|
||||||
return
|
return
|
||||||
@@ -289,9 +334,9 @@ type commentsDetail struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
Avatar string `json:"avatar"`
|
Avatar string `json:"avatar"`
|
||||||
CreatedAt string `json:"createdAt"`
|
|
||||||
CreatedAtStr string `json:"createdAtStr"`
|
CreatedAtStr string `json:"createdAtStr"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
|
IsDeleted bool `json:"isDeleted"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleDisqus(c *gin.Context) {
|
func HandleDisqus(c *gin.Context) {
|
||||||
@@ -302,8 +347,12 @@ func HandleDisqus(c *gin.Context) {
|
|||||||
if artc := Ei.MapArticles[slug]; artc != nil {
|
if artc := Ei.MapArticles[slug]; artc != nil {
|
||||||
dcs.Data.Thread = artc.Thread
|
dcs.Data.Thread = artc.Thread
|
||||||
}
|
}
|
||||||
postsList := PostsList(slug, cursor)
|
postsList, err := PostsList(slug, cursor)
|
||||||
if postsList != nil {
|
if err != nil {
|
||||||
|
logd.Error(err)
|
||||||
|
dcs.ErrNo = FAIL
|
||||||
|
dcs.ErrMsg = "系统错误"
|
||||||
|
} else {
|
||||||
dcs.ErrNo = postsList.Code
|
dcs.ErrNo = postsList.Code
|
||||||
if postsList.Cursor.HasNext {
|
if postsList.Cursor.HasNext {
|
||||||
dcs.Data.Next = postsList.Cursor.Next
|
dcs.Data.Next = postsList.Cursor.Next
|
||||||
@@ -320,30 +369,35 @@ func HandleDisqus(c *gin.Context) {
|
|||||||
Parent: v.Parent,
|
Parent: v.Parent,
|
||||||
Url: v.Author.ProfileUrl,
|
Url: v.Author.ProfileUrl,
|
||||||
Avatar: v.Author.Avatar.Cache,
|
Avatar: v.Author.Avatar.Cache,
|
||||||
CreatedAt: v.CreatedAt,
|
|
||||||
CreatedAtStr: ConvertStr(v.CreatedAt),
|
CreatedAtStr: ConvertStr(v.CreatedAt),
|
||||||
Message: v.Message,
|
Message: v.Message,
|
||||||
|
IsDeleted: v.IsDeleted,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
dcs.ErrNo = FAIL
|
|
||||||
dcs.ErrMsg = "系统错误"
|
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, dcs)
|
c.JSON(http.StatusOK, dcs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 发表评论
|
||||||
// [thread:[5279901489] parent:[] identifier:[post-troubleshooting-https] next:[] author_name:[你好] author_email:[chenqijing2@163.com] message:[fdsfdsf]]
|
// [thread:[5279901489] parent:[] identifier:[post-troubleshooting-https] next:[] author_name:[你好] author_email:[chenqijing2@163.com] message:[fdsfdsf]]
|
||||||
|
type DisqusCreate struct {
|
||||||
|
ErrNo int `json:"errno"`
|
||||||
|
ErrMsg string `json:"errmsg"`
|
||||||
|
Data commentsDetail `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
func HandleDisqusCreate(c *gin.Context) {
|
func HandleDisqusCreate(c *gin.Context) {
|
||||||
rep := gin.H{"errno": SUCCESS, "errmsg": ""}
|
resp := &DisqusCreate{}
|
||||||
defer c.JSON(http.StatusOK, rep)
|
defer c.JSON(http.StatusOK, resp)
|
||||||
|
|
||||||
msg := c.PostForm("message")
|
msg := c.PostForm("message")
|
||||||
email := c.PostForm("author_email")
|
email := c.PostForm("author_email")
|
||||||
name := c.PostForm("author_name")
|
name := c.PostForm("author_name")
|
||||||
thread := c.PostForm("thread")
|
thread := c.PostForm("thread")
|
||||||
identifier := c.PostForm("identifier")
|
identifier := c.PostForm("identifier")
|
||||||
if msg == "" || email == "" || name == "" || thread == "" || identifier == "" {
|
if msg == "" || email == "" || name == "" || thread == "" || identifier == "" {
|
||||||
rep["errno"] = FAIL
|
resp.ErrNo = FAIL
|
||||||
rep["errmsg"] = "参数错误"
|
resp.ErrMsg = "参数错误"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pc := &PostCreate{
|
pc := &PostCreate{
|
||||||
@@ -356,16 +410,34 @@ func HandleDisqusCreate(c *gin.Context) {
|
|||||||
IpAddress: c.ClientIP(),
|
IpAddress: c.ClientIP(),
|
||||||
}
|
}
|
||||||
|
|
||||||
id := PostComment(pc)
|
postDetail, err := PostComment(pc)
|
||||||
if id == "" {
|
if err != nil {
|
||||||
rep["errno"] = FAIL
|
logd.Error(err)
|
||||||
rep["errmsg"] = "系统错误"
|
resp.ErrNo = FAIL
|
||||||
|
resp.ErrMsg = "系统错误"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rep["errno"] = SUCCESS
|
err = PostApprove(postDetail.Response.Id)
|
||||||
rep["data"] = gin.H{"id": id}
|
if err != nil {
|
||||||
|
logd.Error(err)
|
||||||
|
resp.ErrNo = FAIL
|
||||||
|
resp.ErrMsg = "系统错误"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp.ErrNo = SUCCESS
|
||||||
|
resp.Data = commentsDetail{
|
||||||
|
Id: postDetail.Response.Id,
|
||||||
|
Name: name,
|
||||||
|
Parent: postDetail.Response.Parent,
|
||||||
|
Url: postDetail.Response.Author.ProfileUrl,
|
||||||
|
Avatar: postDetail.Response.Author.Avatar.Cache,
|
||||||
|
CreatedAtStr: ConvertStr(postDetail.Response.CreatedAt),
|
||||||
|
Message: postDetail.Response.Message,
|
||||||
|
IsDeleted: postDetail.Response.IsDeleted,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 渲染页面
|
||||||
func RenderHTMLFront(c *gin.Context, name string, data gin.H) {
|
func RenderHTMLFront(c *gin.Context, name string, data gin.H) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err := Tmpl.ExecuteTemplate(&buf, name, data)
|
err := Tmpl.ExecuteTemplate(&buf, name, data)
|
||||||
|
|||||||
72
glide.lock
generated
72
glide.lock
generated
@@ -1,24 +1,28 @@
|
|||||||
hash: bd360fa297ed66950543990f9433cdcdf13c29dd99d9a01b49027e236b2cb9da
|
hash: c733fa4abeda21b59b001578b37a168bd33038d337b61198cc5fd94be8bfdf77
|
||||||
updated: 2017-06-14T20:34:29.429161691+08:00
|
updated: 2017-11-24T22:55:44.759966+08:00
|
||||||
imports:
|
imports:
|
||||||
- name: github.com/boj/redistore
|
- name: github.com/boj/redistore
|
||||||
version: 4562487a4bee9a7c272b72bfaeda4917d0a47ab9
|
version: 4562487a4bee9a7c272b72bfaeda4917d0a47ab9
|
||||||
|
- name: github.com/deepzz0/logd
|
||||||
|
version: 2bbe53d047054777f3a171cdfc6dca7aa9f8af78
|
||||||
- name: github.com/eiblog/blackfriday
|
- name: github.com/eiblog/blackfriday
|
||||||
version: c0ec111761ae784fe31cc076f2fa0e2d2216d623
|
version: c0ec111761ae784fe31cc076f2fa0e2d2216d623
|
||||||
- name: github.com/eiblog/utils
|
- name: github.com/eiblog/utils
|
||||||
version: ad2f63940c4f16d0dbfc3f4df59e8cb7af0f80ec
|
version: d4873fe859435121012ce87b6b8407bd09f89ce0
|
||||||
subpackages:
|
subpackages:
|
||||||
- logd
|
- logd
|
||||||
- mgo
|
- mgo
|
||||||
- tmpl
|
- tmpl
|
||||||
- uuid
|
- uuid
|
||||||
- name: github.com/garyburd/redigo
|
- name: github.com/garyburd/redigo
|
||||||
version: 95d11dba2d44531bdb8022752b98912baafae03a
|
version: 4a7d9db4333c65288dd5fc8c8de7d1f229bb09ec
|
||||||
subpackages:
|
subpackages:
|
||||||
- internal
|
- internal
|
||||||
- redis
|
- redis
|
||||||
|
- name: github.com/gin-gonic/autotls
|
||||||
|
version: 8ca25fbde72bb72a00466215b94b489c71fcb815
|
||||||
- name: github.com/gin-gonic/contrib
|
- name: github.com/gin-gonic/contrib
|
||||||
version: d4fc5a96cc0d29cb0e862bb1312dd6f4fedfcaee
|
version: 8f08bc9b92a9734916abda03656c5f1b99ad10be
|
||||||
subpackages:
|
subpackages:
|
||||||
- sessions
|
- sessions
|
||||||
- name: github.com/gin-gonic/gin
|
- name: github.com/gin-gonic/gin
|
||||||
@@ -35,19 +39,38 @@ imports:
|
|||||||
- name: github.com/gorilla/securecookie
|
- name: github.com/gorilla/securecookie
|
||||||
version: e59506cc896acb7f7bf732d4fdf5e25f7ccd8983
|
version: e59506cc896acb7f7bf732d4fdf5e25f7ccd8983
|
||||||
- name: github.com/gorilla/sessions
|
- name: github.com/gorilla/sessions
|
||||||
version: 8b6b4cd75f07f7ee036eb37b8127bd40ab1efc49
|
version: a3acf13e802c358d65f249324d14ed24aac11370
|
||||||
- name: github.com/manucorporat/sse
|
- name: github.com/manucorporat/sse
|
||||||
version: ee05b128a739a0fb76c7ebd3ae4810c1de808d6d
|
version: ee05b128a739a0fb76c7ebd3ae4810c1de808d6d
|
||||||
- name: github.com/mattn/go-isatty
|
- name: github.com/mattn/go-isatty
|
||||||
version: fc9e8d8ef48496124e79ae0df75490096eccf6fe
|
version: 6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c
|
||||||
|
- name: github.com/qiniu/api.v7
|
||||||
|
version: b7c7d6a2ce0aff8e5e7d14c39c3cde867efa1123
|
||||||
|
subpackages:
|
||||||
|
- auth/qbox
|
||||||
|
- conf
|
||||||
|
- storage
|
||||||
|
- name: github.com/qiniu/x
|
||||||
|
version: f512abcf45ab4e2ba0fd4784c57b53d495997d66
|
||||||
|
subpackages:
|
||||||
|
- bytes.v7
|
||||||
|
- bytes.v7/seekable
|
||||||
|
- ctype.v7
|
||||||
|
- rpc.v7
|
||||||
|
- xlog.v7
|
||||||
- name: github.com/shurcooL/sanitized_anchor_name
|
- name: github.com/shurcooL/sanitized_anchor_name
|
||||||
version: 541ff5ee47f1dddf6a5281af78307d921524bcb5
|
version: 86672fcb3f950f35f2e675df2240550f2a50762f
|
||||||
|
- name: golang.org/x/crypto
|
||||||
|
version: b080dc9a8c480b08e698fb1219160d598526310f
|
||||||
|
subpackages:
|
||||||
|
- acme
|
||||||
|
- acme/autocert
|
||||||
- name: golang.org/x/net
|
- name: golang.org/x/net
|
||||||
version: f315505cf3349909cdf013ea56690da34e96a451
|
version: f315505cf3349909cdf013ea56690da34e96a451
|
||||||
subpackages:
|
subpackages:
|
||||||
- context
|
- context
|
||||||
- name: golang.org/x/sys
|
- name: golang.org/x/sys
|
||||||
version: 0b25a408a50076fbbcae6b7ac0ea5fbb0b085e79
|
version: a13efeb2fd213cf4be7227992aa54519af3b2ac0
|
||||||
subpackages:
|
subpackages:
|
||||||
- unix
|
- unix
|
||||||
- name: gopkg.in/go-playground/validator.v8
|
- name: gopkg.in/go-playground/validator.v8
|
||||||
@@ -60,24 +83,23 @@ imports:
|
|||||||
- internal/sasl
|
- internal/sasl
|
||||||
- internal/scram
|
- internal/scram
|
||||||
- name: gopkg.in/yaml.v2
|
- name: gopkg.in/yaml.v2
|
||||||
version: cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b
|
version: 287cf08546ab5e7e37d55a84f7ed3fd1db036de5
|
||||||
- name: qiniupkg.com/api.v7
|
|
||||||
version: 89344a711feec1d800c77a80d0865de936dc394d
|
|
||||||
subpackages:
|
|
||||||
- api
|
|
||||||
- auth/qbox
|
|
||||||
- conf
|
|
||||||
- kodo
|
|
||||||
- kodocli
|
|
||||||
- name: qiniupkg.com/x
|
- name: qiniupkg.com/x
|
||||||
version: f512abcf45ab4e2ba0fd4784c57b53d495997d66
|
version: 946c4a16076d6d98aeb78619e2bd4012357f7228
|
||||||
subpackages:
|
subpackages:
|
||||||
- bytes.v7
|
- bytes.v7
|
||||||
- bytes.v7/seekable
|
|
||||||
- ctype.v7
|
|
||||||
- log.v7
|
- log.v7
|
||||||
- reqid.v7
|
- reqid.v7
|
||||||
- rpc.v7
|
testImports:
|
||||||
- url.v7
|
- name: github.com/davecgh/go-spew
|
||||||
- xlog.v7
|
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
|
||||||
testImports: []
|
subpackages:
|
||||||
|
- spew
|
||||||
|
- name: github.com/pmezard/go-difflib
|
||||||
|
version: 792786c7400a136282c1664665ae0a8db921c6c2
|
||||||
|
subpackages:
|
||||||
|
- difflib
|
||||||
|
- name: github.com/stretchr/testify
|
||||||
|
version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0
|
||||||
|
subpackages:
|
||||||
|
- assert
|
||||||
|
|||||||
16
glide.yaml
16
glide.yaml
@@ -1,5 +1,6 @@
|
|||||||
package: github.com/eiblog/eiblog
|
package: github.com/eiblog/eiblog
|
||||||
import:
|
import:
|
||||||
|
- package: github.com/deepzz0/logd
|
||||||
- package: github.com/eiblog/blackfriday
|
- package: github.com/eiblog/blackfriday
|
||||||
- package: github.com/eiblog/utils
|
- package: github.com/eiblog/utils
|
||||||
subpackages:
|
subpackages:
|
||||||
@@ -7,19 +8,20 @@ import:
|
|||||||
- mgo
|
- mgo
|
||||||
- tmpl
|
- tmpl
|
||||||
- uuid
|
- uuid
|
||||||
|
- package: github.com/gin-gonic/autotls
|
||||||
- package: github.com/gin-gonic/contrib
|
- package: github.com/gin-gonic/contrib
|
||||||
subpackages:
|
subpackages:
|
||||||
- sessions
|
- sessions
|
||||||
- package: github.com/gin-gonic/gin
|
- package: github.com/gin-gonic/gin
|
||||||
version: ~1.1.4
|
- package: github.com/qiniu/api.v7
|
||||||
|
subpackages:
|
||||||
|
- auth/qbox
|
||||||
|
- storage
|
||||||
- package: gopkg.in/mgo.v2
|
- package: gopkg.in/mgo.v2
|
||||||
subpackages:
|
subpackages:
|
||||||
- bson
|
- bson
|
||||||
- package: gopkg.in/yaml.v2
|
- package: gopkg.in/yaml.v2
|
||||||
- package: qiniupkg.com/api.v7
|
testImport:
|
||||||
|
- package: github.com/stretchr/testify
|
||||||
subpackages:
|
subpackages:
|
||||||
- kodo
|
- assert
|
||||||
- kodocli
|
|
||||||
- package: qiniupkg.com/x
|
|
||||||
subpackages:
|
|
||||||
- url.v7
|
|
||||||
|
|||||||
21
helper.go
21
helper.go
@@ -18,6 +18,7 @@ const (
|
|||||||
FAIL
|
FAIL
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 月份转换
|
||||||
var monthToDays = map[time.Month]int{
|
var monthToDays = map[time.Month]int{
|
||||||
time.January: 31,
|
time.January: 31,
|
||||||
time.February: 28,
|
time.February: 28,
|
||||||
@@ -43,14 +44,17 @@ func EncryptPasswd(name, pass string) string {
|
|||||||
return fmt.Sprintf("%x", h.Sum(nil))
|
return fmt.Sprintf("%x", h.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证密码
|
||||||
func VerifyPasswd(origin, name, input string) bool {
|
func VerifyPasswd(origin, name, input string) bool {
|
||||||
return origin == EncryptPasswd(name, input)
|
return origin == EncryptPasswd(name, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 随机 uuid
|
||||||
func RandUUIDv4() string {
|
func RandUUIDv4() string {
|
||||||
return uuid.NewV4().String()
|
return uuid.NewV4().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 读取目录
|
||||||
func ReadDir(dir string, filter func(name string) bool) (files []string) {
|
func ReadDir(dir string, filter func(name string) bool) (files []string) {
|
||||||
fis, err := ioutil.ReadDir(dir)
|
fis, err := ioutil.ReadDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -69,16 +73,18 @@ func ReadDir(dir string, filter func(name string) bool) (files []string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 去掉 html tag
|
||||||
func IgnoreHtmlTag(src string) string {
|
func IgnoreHtmlTag(src string) string {
|
||||||
//去除所有尖括号内的HTML代码
|
// 去除所有尖括号内的HTML代码
|
||||||
re, _ := regexp.Compile("\\<[\\S\\s]+?\\>")
|
re, _ := regexp.Compile(`<[\S\s]+?>`)
|
||||||
src = re.ReplaceAllString(src, "")
|
src = re.ReplaceAllString(src, "")
|
||||||
|
|
||||||
//去除换行符
|
// 去除换行符
|
||||||
re, _ = regexp.Compile("\\s{2,}")
|
re, _ = regexp.Compile(`\s+`)
|
||||||
return re.ReplaceAllString(src, "")
|
return re.ReplaceAllString(src, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取第一张图片
|
||||||
func PickFirstImage(html string) string {
|
func PickFirstImage(html string) string {
|
||||||
re, _ := regexp.Compile(`data-src="(.*?)"`)
|
re, _ := regexp.Compile(`data-src="(.*?)"`)
|
||||||
sli := re.FindAllStringSubmatch(html, 1)
|
sli := re.FindAllStringSubmatch(html, 1)
|
||||||
@@ -98,15 +104,16 @@ const (
|
|||||||
YEARS_AGO = "%d年前"
|
YEARS_AGO = "%d年前"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 时间转换为间隔
|
||||||
func ConvertStr(str string) string {
|
func ConvertStr(str string) string {
|
||||||
t, err := time.ParseInLocation("2006-01-02T15:04:05", str, time.UTC)
|
t, err := time.ParseInLocation("2006-01-02T15:04:05", str, time.UTC)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logd.Error(err, str)
|
logd.Error(err, str)
|
||||||
return JUST_NOW
|
return JUST_NOW
|
||||||
}
|
}
|
||||||
now := time.Now()
|
now := time.Now().UTC()
|
||||||
y1, m1, d1 := t.Date()
|
y1, m1, d1 := t.Date()
|
||||||
y2, m2, d2 := now.UTC().Date()
|
y2, m2, d2 := now.Date()
|
||||||
h1, mi1, s1 := t.Clock()
|
h1, mi1, s1 := t.Clock()
|
||||||
h2, mi2, s2 := now.Clock()
|
h2, mi2, s2 := now.Clock()
|
||||||
if y := y2 - y1; y > 1 || (y == 1 && m2-m1 >= 0) {
|
if y := y2 - y1; y > 1 || (y == 1 && m2-m1 >= 0) {
|
||||||
@@ -123,6 +130,7 @@ func ConvertStr(str string) string {
|
|||||||
return JUST_NOW
|
return JUST_NOW
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取天数
|
||||||
func dayIn(year int, m time.Month) int {
|
func dayIn(year int, m time.Month) int {
|
||||||
if m == time.February && isLeap(year) {
|
if m == time.February && isLeap(year) {
|
||||||
return 29
|
return 29
|
||||||
@@ -130,6 +138,7 @@ func dayIn(year int, m time.Month) int {
|
|||||||
return monthToDays[m]
|
return monthToDays[m]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 是否是闰年
|
||||||
func isLeap(year int) bool {
|
func isLeap(year int) bool {
|
||||||
return year%4 == 0 && (year%100 != 0 || year%400 == 0)
|
return year%4 == 0 && (year%100 != 0 || year%400 == 0)
|
||||||
}
|
}
|
||||||
|
|||||||
58
helper_test.go
Normal file
58
helper_test.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
// Package main provides ...
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReadDir(t *testing.T) {
|
||||||
|
files := ReadDir("setting", func(name string) bool { return false })
|
||||||
|
assert.Len(t, files, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIgnoreHtmlTag(t *testing.T) {
|
||||||
|
testStr := []string{
|
||||||
|
"<script>hello</script>",
|
||||||
|
}
|
||||||
|
|
||||||
|
expectStr := []string{
|
||||||
|
"hello",
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, v := range testStr {
|
||||||
|
assert.Equal(t, expectStr[i], IgnoreHtmlTag(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPickFirstImage(t *testing.T) {
|
||||||
|
testStr := []string{
|
||||||
|
`<img width="480" height="310" alt="acme_aliyun_1" src="https://st.deepzz.com/blog/img/acme_aliyun_1.jpg">`,
|
||||||
|
`<img width="480" height="310" alt="acme_aliyun_1" data-src="https://st.deepzz.com/acme_aliyun_1.jpg"><img width="480" height="310" alt="acme_aliyun_1" src="https://st.deepzz.com/blog/img/acme_aliyun_1.jpg">`,
|
||||||
|
}
|
||||||
|
|
||||||
|
expectStr := []string{
|
||||||
|
"",
|
||||||
|
"https://st.deepzz.com/acme_aliyun_1.jpg",
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, v := range testStr {
|
||||||
|
assert.Equal(t, expectStr[i], PickFirstImage(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCovertStr(t *testing.T) {
|
||||||
|
testStr := []string{
|
||||||
|
time.Now().Format("2006-01-02T15:04:05"),
|
||||||
|
}
|
||||||
|
|
||||||
|
expectStr := []string{
|
||||||
|
JUST_NOW,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, v := range testStr {
|
||||||
|
assert.Equal(t, expectStr[i], ConvertStr(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
1
ping.go
1
ping.go
@@ -105,6 +105,7 @@ func init() {
|
|||||||
Pings = append(Pings, pr)
|
Pings = append(Pings, pr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ping
|
||||||
func DoPings(slug string) {
|
func DoPings(slug string) {
|
||||||
for _, p := range Pings {
|
for _, p := range Pings {
|
||||||
go p.PingFunc(slug)
|
go p.PingFunc(slug)
|
||||||
|
|||||||
78
qiniu.go
78
qiniu.go
@@ -4,20 +4,14 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/eiblog/eiblog/setting"
|
"github.com/eiblog/eiblog/setting"
|
||||||
"qiniupkg.com/api.v7/kodo"
|
"github.com/qiniu/api.v7/auth/qbox"
|
||||||
"qiniupkg.com/api.v7/kodocli"
|
"github.com/qiniu/api.v7/storage"
|
||||||
url "qiniupkg.com/x/url.v7"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var qiniu_cfg = &kodo.Config{
|
|
||||||
AccessKey: setting.Conf.Kodo.AccessKey,
|
|
||||||
SecretKey: setting.Conf.Kodo.SecretKey,
|
|
||||||
Scheme: "https",
|
|
||||||
}
|
|
||||||
|
|
||||||
type bucket struct {
|
type bucket struct {
|
||||||
name string
|
name string
|
||||||
domain string
|
domain string
|
||||||
@@ -30,71 +24,79 @@ type PutRet struct {
|
|||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 进度条
|
||||||
func onProgress(fsize, uploaded int64) {
|
func onProgress(fsize, uploaded int64) {
|
||||||
d := int(float64(uploaded) / float64(fsize) * 100)
|
d := int(float64(uploaded) / float64(fsize) * 100)
|
||||||
if fsize == uploaded {
|
if fsize == uploaded {
|
||||||
fmt.Printf("\rUpload completed! ")
|
fmt.Printf("\rUpload completed! \n")
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("\r%02d%% uploaded ", int(d))
|
fmt.Printf("\r%02d%% uploaded ", int(d))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 上传文件
|
||||||
func FileUpload(name string, size int64, data io.Reader) (string, error) {
|
func FileUpload(name string, size int64, data io.Reader) (string, error) {
|
||||||
if setting.Conf.Kodo.AccessKey == "" || setting.Conf.Kodo.SecretKey == "" {
|
if setting.Conf.Qiniu.AccessKey == "" || setting.Conf.Qiniu.SecretKey == "" {
|
||||||
return "", errors.New("qiniu config error")
|
return "", errors.New("qiniu config error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建一个client
|
|
||||||
c := kodo.New(0, qiniu_cfg)
|
|
||||||
|
|
||||||
// 设置上传的策略
|
|
||||||
policy := &kodo.PutPolicy{
|
|
||||||
Scope: setting.Conf.Kodo.Name,
|
|
||||||
Expires: 3600,
|
|
||||||
InsertOnly: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成一个上传token
|
|
||||||
token := c.MakeUptoken(policy)
|
|
||||||
// 构建一个uploader
|
|
||||||
zone := 0
|
|
||||||
uploader := kodocli.NewUploader(zone, nil)
|
|
||||||
|
|
||||||
key := getKey(name)
|
key := getKey(name)
|
||||||
if key == "" {
|
if key == "" {
|
||||||
return "", errors.New("不支持的文件类型")
|
return "", errors.New("不支持的文件类型")
|
||||||
}
|
}
|
||||||
|
|
||||||
var ret PutRet
|
mac := qbox.NewMac(setting.Conf.Qiniu.AccessKey, setting.Conf.Qiniu.SecretKey)
|
||||||
var extra = kodocli.PutExtra{OnProgress: onProgress}
|
// 设置上传的策略
|
||||||
err := uploader.Put(nil, &ret, token, key, data, size, &extra)
|
putPolicy := &storage.PutPolicy{
|
||||||
|
Scope: setting.Conf.Qiniu.Bucket,
|
||||||
|
Expires: 3600,
|
||||||
|
InsertOnly: 1,
|
||||||
|
}
|
||||||
|
// 上传token
|
||||||
|
upToken := putPolicy.UploadToken(mac)
|
||||||
|
|
||||||
|
// 上传配置
|
||||||
|
cfg := &storage.Config{
|
||||||
|
Zone: &storage.ZoneHuadong,
|
||||||
|
UseHTTPS: true,
|
||||||
|
}
|
||||||
|
// uploader
|
||||||
|
uploader := storage.NewFormUploader(cfg)
|
||||||
|
ret := new(storage.PutRet)
|
||||||
|
putExtra := &storage.PutExtra{OnProgress: onProgress}
|
||||||
|
err := uploader.Put(nil, ret, upToken, key, data, size, putExtra)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
url := "https://" + setting.Conf.Kodo.Domain + "/" + url.Escape(key)
|
url := "https://" + setting.Conf.Qiniu.Domain + "/" + url.QueryEscape(key)
|
||||||
return url, nil
|
return url, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 删除文件
|
||||||
func FileDelete(name string) error {
|
func FileDelete(name string) error {
|
||||||
// new一个Bucket管理对象
|
|
||||||
c := kodo.New(0, qiniu_cfg)
|
|
||||||
p := c.Bucket(setting.Conf.Kodo.Name)
|
|
||||||
|
|
||||||
key := getKey(name)
|
key := getKey(name)
|
||||||
if key == "" {
|
if key == "" {
|
||||||
return errors.New("不支持的文件类型")
|
return errors.New("不支持的文件类型")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用Delete方法删除文件
|
mac := qbox.NewMac(setting.Conf.Qiniu.AccessKey, setting.Conf.Qiniu.SecretKey)
|
||||||
err := p.Delete(nil, key)
|
// 上传配置
|
||||||
// 打印返回值以及出错信息
|
cfg := &storage.Config{
|
||||||
|
Zone: &storage.ZoneHuadong,
|
||||||
|
UseHTTPS: true,
|
||||||
|
}
|
||||||
|
// manager
|
||||||
|
bucketManager := storage.NewBucketManager(mac, cfg)
|
||||||
|
// Delete
|
||||||
|
err := bucketManager.Delete(setting.Conf.Qiniu.Bucket, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 修复路径
|
||||||
func getKey(name string) string {
|
func getKey(name string) string {
|
||||||
ext := filepath.Ext(name)
|
ext := filepath.Ext(name)
|
||||||
var key string
|
var key string
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestUpload(t *testing.T) {
|
func TestUpload(t *testing.T) {
|
||||||
path := "/Users/chen/Desktop/png-MicroService-by-StuQ.png"
|
path := "qiniu.go"
|
||||||
file, err := os.Open(path)
|
file, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
info, _ := file.Stat()
|
info, _ := file.Stat()
|
||||||
url, err := FileUpload(info.Name(), info.Size(), file)
|
url, err := FileUpload(info.Name(), info.Size(), file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
43
router.go
43
router.go
@@ -3,12 +3,13 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/eiblog/eiblog/setting"
|
"github.com/eiblog/eiblog/setting"
|
||||||
"github.com/eiblog/utils/logd"
|
"github.com/eiblog/utils/logd"
|
||||||
"github.com/eiblog/utils/tmpl"
|
"github.com/eiblog/utils/tmpl"
|
||||||
|
"github.com/gin-gonic/autotls"
|
||||||
"github.com/gin-gonic/contrib/sessions"
|
"github.com/gin-gonic/contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -19,10 +20,12 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
// 运行模式
|
||||||
if setting.Conf.RunMode == setting.PROD {
|
if setting.Conf.RunMode == setting.PROD {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
logd.SetLevel(logd.Lerror)
|
logd.SetLevel(logd.Lerror)
|
||||||
}
|
}
|
||||||
|
|
||||||
router = gin.Default()
|
router = gin.Default()
|
||||||
store := sessions.NewCookieStore([]byte("eiblog321"))
|
store := sessions.NewCookieStore([]byte("eiblog321"))
|
||||||
store.Options(sessions.Options{
|
store.Options(sessions.Options{
|
||||||
@@ -62,6 +65,8 @@ func init() {
|
|||||||
router.GET("/opensearch.xml", HandleOpenSearch)
|
router.GET("/opensearch.xml", HandleOpenSearch)
|
||||||
router.GET("/sitemap.xml", HandleSitemap)
|
router.GET("/sitemap.xml", HandleSitemap)
|
||||||
router.GET("/robots.txt", HandleRobots)
|
router.GET("/robots.txt", HandleRobots)
|
||||||
|
router.GET("/crossdomain.xml", HandleCrossDomain)
|
||||||
|
router.GET("/favicon.ico", HandleFavicon)
|
||||||
// 后台相关
|
// 后台相关
|
||||||
admin := router.Group("/admin")
|
admin := router.Group("/admin")
|
||||||
admin.GET("/login", HandleLogin)
|
admin.GET("/login", HandleLogin)
|
||||||
@@ -87,6 +92,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 开始运行
|
||||||
func Run() {
|
func Run() {
|
||||||
var (
|
var (
|
||||||
endRunning = make(chan bool, 1)
|
endRunning = make(chan bool, 1)
|
||||||
@@ -94,25 +100,38 @@ func Run() {
|
|||||||
)
|
)
|
||||||
if setting.Conf.Mode.EnableHttp {
|
if setting.Conf.Mode.EnableHttp {
|
||||||
go func() {
|
go func() {
|
||||||
logd.Infof("http server Running on %d\n", setting.Conf.Mode.HttpPort)
|
logd.Printf("http server Running on %d\n", setting.Conf.Mode.HttpPort)
|
||||||
err = router.Run(fmt.Sprintf(":%d", setting.Conf.Mode.HttpPort))
|
err = router.Run(fmt.Sprintf(":%d", setting.Conf.Mode.HttpPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logd.Info("ListenAndServe: ", err)
|
logd.Error("ListenAndServe: ", err)
|
||||||
time.Sleep(100 * time.Microsecond)
|
time.Sleep(100 * time.Microsecond)
|
||||||
endRunning <- true
|
endRunning <- true
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
if setting.Conf.Mode.EnableHttps {
|
if setting.Conf.Mode.EnableHttps {
|
||||||
go func() {
|
if setting.Conf.Mode.AutoCert {
|
||||||
logd.Infof("https server Running on %d\n", setting.Conf.Mode.HttpsPort)
|
go func() {
|
||||||
err = router.RunTLS(fmt.Sprintf(":%d", setting.Conf.Mode.HttpsPort), setting.Conf.Mode.CertFile, setting.Conf.Mode.KeyFile)
|
logd.Print("https server Running on 443")
|
||||||
if err != nil {
|
err = autotls.Run(router, setting.Conf.Mode.Domain)
|
||||||
logd.Info("ListenAndServe: ", err)
|
if err != nil {
|
||||||
time.Sleep(100 * time.Microsecond)
|
logd.Error("ListenAndServe: ", err)
|
||||||
endRunning <- true
|
time.Sleep(100 * time.Microsecond)
|
||||||
}
|
endRunning <- true
|
||||||
}()
|
}
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
go func() {
|
||||||
|
logd.Printf("https server Running on %d\n", setting.Conf.Mode.HttpsPort)
|
||||||
|
err = router.RunTLS(fmt.Sprintf(":%d", setting.Conf.Mode.HttpsPort),
|
||||||
|
setting.Conf.Mode.CertFile, setting.Conf.Mode.KeyFile)
|
||||||
|
if err != nil {
|
||||||
|
logd.Error("ListenAndServe: ", err)
|
||||||
|
time.Sleep(100 * time.Microsecond)
|
||||||
|
endRunning <- true
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
<-endRunning
|
<-endRunning
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DEV = "dev"
|
DEV = "dev" // 该模式会输出 debug 等信息
|
||||||
PROD = "prod"
|
PROD = "prod" // 该模式用于生产环境
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -19,55 +19,68 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
StaticVersion int // 当前静态文件版本
|
|
||||||
RunMode string // 运行模式
|
RunMode string // 运行模式
|
||||||
Trash int // 回收箱文章保留时间
|
StaticVersion int // 当前静态文件版本
|
||||||
Clean int // 清理回收箱频率
|
FeedrURL string // superfeedr url
|
||||||
PageNum int // 前端每页文章数量
|
HotWords []string // 热搜词
|
||||||
PageSize int // 后台每页文章数量
|
PingRPCs []string // ping rpc 地址
|
||||||
Length int // 自动截取预览长度
|
General struct {
|
||||||
Identifier string // 截取标示
|
PageNum int // 前端每页文章数量
|
||||||
Description string // 文章描述前缀
|
PageSize int // 后台每页文章数量
|
||||||
Favicon string // icon地址
|
StartID int32 // 文章起始id
|
||||||
StartID int32 // 文章起始id
|
DescPrefix string // 文章描述前缀
|
||||||
SearchURL string // elasticsearch 地址
|
Identifier string // 文章截取标示
|
||||||
Disqus struct { // 获取文章数量相关
|
Length int // 文章自动截取预览长度
|
||||||
ShortName string
|
Trash int // 回收箱文章保留时间
|
||||||
PublicKey string
|
Clean int // 清理回收箱频率
|
||||||
PostsCount string
|
|
||||||
PostsList string
|
|
||||||
PostCreate string
|
|
||||||
Interval int
|
|
||||||
}
|
}
|
||||||
HotWords []string // 热搜词
|
Disqus struct { // 获取文章数量相关
|
||||||
Google struct { // 谷歌统计
|
ShortName string
|
||||||
|
PublicKey string
|
||||||
|
AccessToken string
|
||||||
|
PostsCount string
|
||||||
|
PostsList string
|
||||||
|
PostCreate string
|
||||||
|
PostApprove string
|
||||||
|
Embed string
|
||||||
|
Interval int
|
||||||
|
}
|
||||||
|
Google struct { // 谷歌统计
|
||||||
|
URL string
|
||||||
Tid string
|
Tid string
|
||||||
V string
|
V string
|
||||||
T string
|
T string
|
||||||
}
|
}
|
||||||
Kodo struct { // 七牛CDN
|
Qiniu struct { // 七牛CDN
|
||||||
Name string
|
Bucket string
|
||||||
Domain string
|
Domain string
|
||||||
AccessKey string
|
AccessKey string
|
||||||
SecretKey string
|
SecretKey string
|
||||||
}
|
}
|
||||||
Mode RunMode // 运行模式
|
Mode struct { // 运行模式
|
||||||
|
EnableHttp bool
|
||||||
|
HttpPort int
|
||||||
|
EnableHttps bool
|
||||||
|
HttpsPort int
|
||||||
|
CertFile string
|
||||||
|
KeyFile string
|
||||||
|
AutoCert bool
|
||||||
|
Domain string
|
||||||
|
}
|
||||||
Twitter struct { // twitter信息
|
Twitter struct { // twitter信息
|
||||||
Card string
|
Card string
|
||||||
Site string
|
Site string
|
||||||
Image string
|
Image string
|
||||||
Address string
|
Address string
|
||||||
}
|
}
|
||||||
FeedrURL string // superfeedr url
|
Account struct { // account 账户
|
||||||
PingRPCs []string // ping rpc 地址
|
|
||||||
Account struct {
|
|
||||||
Username string // *
|
Username string // *
|
||||||
Password string // *
|
Password string // *
|
||||||
Email string
|
Email string
|
||||||
PhoneNumber string
|
PhoneNumber string
|
||||||
Address string
|
Address string
|
||||||
}
|
}
|
||||||
Blogger struct { // 初始化数据
|
Blogger struct { // blog info 博客信息
|
||||||
BlogName string
|
BlogName string
|
||||||
SubTitle string
|
SubTitle string
|
||||||
BeiAn string
|
BeiAn string
|
||||||
@@ -76,16 +89,6 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type RunMode struct {
|
|
||||||
EnableHttp bool
|
|
||||||
HttpPort int
|
|
||||||
EnableHttps bool
|
|
||||||
HttpsPort int
|
|
||||||
CertFile string
|
|
||||||
KeyFile string
|
|
||||||
Domain string
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// 初始化配置
|
// 初始化配置
|
||||||
data, err := ioutil.ReadFile("conf/app.yml")
|
data, err := ioutil.ReadFile("conf/app.yml")
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
package setting
|
package setting
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
func TestInit(t *testing.T) {
|
||||||
init()
|
assert.NotNil(t, Conf)
|
||||||
fmt.Printf("%v\n", *Conf)
|
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB |
22
vendor/github.com/davecgh/go-spew/.gitignore
generated
vendored
Normal file
22
vendor/github.com/davecgh/go-spew/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
11
vendor/github.com/davecgh/go-spew/.travis.yml
generated
vendored
Normal file
11
vendor/github.com/davecgh/go-spew/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
language: go
|
||||||
|
go: 1.2
|
||||||
|
install:
|
||||||
|
- go get -v code.google.com/p/go.tools/cmd/cover
|
||||||
|
script:
|
||||||
|
- go test -v -tags=disableunsafe ./spew
|
||||||
|
- go test -v -tags=testcgo ./spew -covermode=count -coverprofile=profile.cov
|
||||||
|
after_success:
|
||||||
|
- go get -v github.com/mattn/goveralls
|
||||||
|
- export PATH=$PATH:$HOME/gopath/bin
|
||||||
|
- goveralls -coverprofile=profile.cov -service=travis-ci
|
||||||
13
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
Normal file
13
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
Copyright (c) 2012-2013 Dave Collins <dave@davec.name>
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
194
vendor/github.com/davecgh/go-spew/README.md
generated
vendored
Normal file
194
vendor/github.com/davecgh/go-spew/README.md
generated
vendored
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
go-spew
|
||||||
|
=======
|
||||||
|
|
||||||
|
[]
|
||||||
|
(https://travis-ci.org/davecgh/go-spew) [![Coverage Status]
|
||||||
|
(https://coveralls.io/repos/davecgh/go-spew/badge.png?branch=master)]
|
||||||
|
(https://coveralls.io/r/davecgh/go-spew?branch=master)
|
||||||
|
|
||||||
|
Go-spew implements a deep pretty printer for Go data structures to aid in
|
||||||
|
debugging. A comprehensive suite of tests with 100% test coverage is provided
|
||||||
|
to ensure proper functionality. See `test_coverage.txt` for the gocov coverage
|
||||||
|
report. Go-spew is licensed under the liberal ISC license, so it may be used in
|
||||||
|
open source or commercial projects.
|
||||||
|
|
||||||
|
If you're interested in reading about how this package came to life and some
|
||||||
|
of the challenges involved in providing a deep pretty printer, there is a blog
|
||||||
|
post about it
|
||||||
|
[here](https://blog.cyphertite.com/go-spew-a-journey-into-dumping-go-data-structures/).
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
[]
|
||||||
|
(http://godoc.org/github.com/davecgh/go-spew/spew)
|
||||||
|
|
||||||
|
Full `go doc` style documentation for the project can be viewed online without
|
||||||
|
installing this package by using the excellent GoDoc site here:
|
||||||
|
http://godoc.org/github.com/davecgh/go-spew/spew
|
||||||
|
|
||||||
|
You can also view the documentation locally once the package is installed with
|
||||||
|
the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to
|
||||||
|
http://localhost:6060/pkg/github.com/davecgh/go-spew/spew
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ go get -u github.com/davecgh/go-spew/spew
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
Add this import line to the file you're working in:
|
||||||
|
|
||||||
|
```Go
|
||||||
|
import "github.com/davecgh/go-spew/spew"
|
||||||
|
```
|
||||||
|
|
||||||
|
To dump a variable with full newlines, indentation, type, and pointer
|
||||||
|
information use Dump, Fdump, or Sdump:
|
||||||
|
|
||||||
|
```Go
|
||||||
|
spew.Dump(myVar1, myVar2, ...)
|
||||||
|
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
||||||
|
str := spew.Sdump(myVar1, myVar2, ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, if you would prefer to use format strings with a compacted inline
|
||||||
|
printing style, use the convenience wrappers Printf, Fprintf, etc with %v (most
|
||||||
|
compact), %+v (adds pointer addresses), %#v (adds types), or %#+v (adds types
|
||||||
|
and pointer addresses):
|
||||||
|
|
||||||
|
```Go
|
||||||
|
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging a Web Application Example
|
||||||
|
|
||||||
|
Here is an example of how you can use `spew.Sdump()` to help debug a web application. Please be sure to wrap your output using the `html.EscapeString()` function for safety reasons. You should also only use this debugging technique in a development environment, never in production.
|
||||||
|
|
||||||
|
```Go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "text/html")
|
||||||
|
fmt.Fprintf(w, "Hi there, %s!", r.URL.Path[1:])
|
||||||
|
fmt.Fprintf(w, "<!--\n" + html.EscapeString(spew.Sdump(w)) + "\n-->")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", handler)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sample Dump Output
|
||||||
|
|
||||||
|
```
|
||||||
|
(main.Foo) {
|
||||||
|
unexportedField: (*main.Bar)(0xf84002e210)({
|
||||||
|
flag: (main.Flag) flagTwo,
|
||||||
|
data: (uintptr) <nil>
|
||||||
|
}),
|
||||||
|
ExportedField: (map[interface {}]interface {}) {
|
||||||
|
(string) "one": (bool) true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
([]uint8) {
|
||||||
|
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||||
|
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||||
|
00000020 31 32 |12|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sample Formatter Output
|
||||||
|
|
||||||
|
Double pointer to a uint8:
|
||||||
|
```
|
||||||
|
%v: <**>5
|
||||||
|
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
||||||
|
%#v: (**uint8)5
|
||||||
|
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
||||||
|
```
|
||||||
|
|
||||||
|
Pointer to circular struct with a uint8 field and a pointer to itself:
|
||||||
|
```
|
||||||
|
%v: <*>{1 <*><shown>}
|
||||||
|
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
||||||
|
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
||||||
|
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Options
|
||||||
|
|
||||||
|
Configuration of spew is handled by fields in the ConfigState type. For
|
||||||
|
convenience, all of the top-level functions use a global state available via the
|
||||||
|
spew.Config global.
|
||||||
|
|
||||||
|
It is also possible to create a ConfigState instance that provides methods
|
||||||
|
equivalent to the top-level functions. This allows concurrent configuration
|
||||||
|
options. See the ConfigState documentation for more details.
|
||||||
|
|
||||||
|
```
|
||||||
|
* Indent
|
||||||
|
String to use for each indentation level for Dump functions.
|
||||||
|
It is a single space by default. A popular alternative is "\t".
|
||||||
|
|
||||||
|
* MaxDepth
|
||||||
|
Maximum number of levels to descend into nested data structures.
|
||||||
|
There is no limit by default.
|
||||||
|
|
||||||
|
* DisableMethods
|
||||||
|
Disables invocation of error and Stringer interface methods.
|
||||||
|
Method invocation is enabled by default.
|
||||||
|
|
||||||
|
* DisablePointerMethods
|
||||||
|
Disables invocation of error and Stringer interface methods on types
|
||||||
|
which only accept pointer receivers from non-pointer variables. This option
|
||||||
|
relies on access to the unsafe package, so it will not have any effect when
|
||||||
|
running in environments without access to the unsafe package such as Google
|
||||||
|
App Engine or with the "disableunsafe" build tag specified.
|
||||||
|
Pointer method invocation is enabled by default.
|
||||||
|
|
||||||
|
* ContinueOnMethod
|
||||||
|
Enables recursion into types after invoking error and Stringer interface
|
||||||
|
methods. Recursion after method invocation is disabled by default.
|
||||||
|
|
||||||
|
* SortKeys
|
||||||
|
Specifies map keys should be sorted before being printed. Use
|
||||||
|
this to have a more deterministic, diffable output. Note that
|
||||||
|
only native types (bool, int, uint, floats, uintptr and string)
|
||||||
|
and types which implement error or Stringer interfaces are supported,
|
||||||
|
with other types sorted according to the reflect.Value.String() output
|
||||||
|
which guarantees display stability. Natural map order is used by
|
||||||
|
default.
|
||||||
|
|
||||||
|
* SpewKeys
|
||||||
|
SpewKeys specifies that, as a last resort attempt, map keys should be
|
||||||
|
spewed to strings and sorted by those strings. This is only considered
|
||||||
|
if SortKeys is true.
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Unsafe Package Dependency
|
||||||
|
|
||||||
|
This package relies on the unsafe package to perform some of the more advanced
|
||||||
|
features, however it also supports a "limited" mode which allows it to work in
|
||||||
|
environments where the unsafe package is not available. By default, it will
|
||||||
|
operate in this mode on Google App Engine. The "disableunsafe" build tag may
|
||||||
|
also be specified to force the package to build without using the unsafe
|
||||||
|
package.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Go-spew is licensed under the liberal ISC License.
|
||||||
22
vendor/github.com/davecgh/go-spew/cov_report.sh
generated
vendored
Normal file
22
vendor/github.com/davecgh/go-spew/cov_report.sh
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# This script uses gocov to generate a test coverage report.
|
||||||
|
# The gocov tool my be obtained with the following command:
|
||||||
|
# go get github.com/axw/gocov/gocov
|
||||||
|
#
|
||||||
|
# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH.
|
||||||
|
|
||||||
|
# Check for gocov.
|
||||||
|
if ! type gocov >/dev/null 2>&1; then
|
||||||
|
echo >&2 "This script requires the gocov tool."
|
||||||
|
echo >&2 "You may obtain it with the following command:"
|
||||||
|
echo >&2 "go get github.com/axw/gocov/gocov"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Only run the cgo tests if gcc is installed.
|
||||||
|
if type gcc >/dev/null 2>&1; then
|
||||||
|
(cd spew && gocov test -tags testcgo | gocov report)
|
||||||
|
else
|
||||||
|
(cd spew && gocov test | gocov report)
|
||||||
|
fi
|
||||||
151
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
Normal file
151
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
// Copyright (c) 2015 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when the code is not running on Google App Engine and "-tags disableunsafe"
|
||||||
|
// is not added to the go build command line.
|
||||||
|
// +build !appengine,!disableunsafe
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||||
|
// not access to the unsafe package is available.
|
||||||
|
UnsafeDisabled = false
|
||||||
|
|
||||||
|
// ptrSize is the size of a pointer on the current arch.
|
||||||
|
ptrSize = unsafe.Sizeof((*byte)(nil))
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// offsetPtr, offsetScalar, and offsetFlag are the offsets for the
|
||||||
|
// internal reflect.Value fields. These values are valid before golang
|
||||||
|
// commit ecccf07e7f9d which changed the format. The are also valid
|
||||||
|
// after commit 82f48826c6c7 which changed the format again to mirror
|
||||||
|
// the original format. Code in the init function updates these offsets
|
||||||
|
// as necessary.
|
||||||
|
offsetPtr = uintptr(ptrSize)
|
||||||
|
offsetScalar = uintptr(0)
|
||||||
|
offsetFlag = uintptr(ptrSize * 2)
|
||||||
|
|
||||||
|
// flagKindWidth and flagKindShift indicate various bits that the
|
||||||
|
// reflect package uses internally to track kind information.
|
||||||
|
//
|
||||||
|
// flagRO indicates whether or not the value field of a reflect.Value is
|
||||||
|
// read-only.
|
||||||
|
//
|
||||||
|
// flagIndir indicates whether the value field of a reflect.Value is
|
||||||
|
// the actual data or a pointer to the data.
|
||||||
|
//
|
||||||
|
// These values are valid before golang commit 90a7c3c86944 which
|
||||||
|
// changed their positions. Code in the init function updates these
|
||||||
|
// flags as necessary.
|
||||||
|
flagKindWidth = uintptr(5)
|
||||||
|
flagKindShift = uintptr(flagKindWidth - 1)
|
||||||
|
flagRO = uintptr(1 << 0)
|
||||||
|
flagIndir = uintptr(1 << 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Older versions of reflect.Value stored small integers directly in the
|
||||||
|
// ptr field (which is named val in the older versions). Versions
|
||||||
|
// between commits ecccf07e7f9d and 82f48826c6c7 added a new field named
|
||||||
|
// scalar for this purpose which unfortunately came before the flag
|
||||||
|
// field, so the offset of the flag field is different for those
|
||||||
|
// versions.
|
||||||
|
//
|
||||||
|
// This code constructs a new reflect.Value from a known small integer
|
||||||
|
// and checks if the size of the reflect.Value struct indicates it has
|
||||||
|
// the scalar field. When it does, the offsets are updated accordingly.
|
||||||
|
vv := reflect.ValueOf(0xf00)
|
||||||
|
if unsafe.Sizeof(vv) == (ptrSize * 4) {
|
||||||
|
offsetScalar = ptrSize * 2
|
||||||
|
offsetFlag = ptrSize * 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit 90a7c3c86944 changed the flag positions such that the low
|
||||||
|
// order bits are the kind. This code extracts the kind from the flags
|
||||||
|
// field and ensures it's the correct type. When it's not, the flag
|
||||||
|
// order has been changed to the newer format, so the flags are updated
|
||||||
|
// accordingly.
|
||||||
|
upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag)
|
||||||
|
upfv := *(*uintptr)(upf)
|
||||||
|
flagKindMask := uintptr((1<<flagKindWidth - 1) << flagKindShift)
|
||||||
|
if (upfv&flagKindMask)>>flagKindShift != uintptr(reflect.Int) {
|
||||||
|
flagKindShift = 0
|
||||||
|
flagRO = 1 << 5
|
||||||
|
flagIndir = 1 << 6
|
||||||
|
|
||||||
|
// Commit adf9b30e5594 modified the flags to separate the
|
||||||
|
// flagRO flag into two bits which specifies whether or not the
|
||||||
|
// field is embedded. This causes flagIndir to move over a bit
|
||||||
|
// and means that flagRO is the combination of either of the
|
||||||
|
// original flagRO bit and the new bit.
|
||||||
|
//
|
||||||
|
// This code detects the change by extracting what used to be
|
||||||
|
// the indirect bit to ensure it's set. When it's not, the flag
|
||||||
|
// order has been changed to the newer format, so the flags are
|
||||||
|
// updated accordingly.
|
||||||
|
if upfv&flagIndir == 0 {
|
||||||
|
flagRO = 3 << 5
|
||||||
|
flagIndir = 1 << 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
|
||||||
|
// the typical safety restrictions preventing access to unaddressable and
|
||||||
|
// unexported data. It works by digging the raw pointer to the underlying
|
||||||
|
// value out of the protected value and generating a new unprotected (unsafe)
|
||||||
|
// reflect.Value to it.
|
||||||
|
//
|
||||||
|
// This allows us to check for implementations of the Stringer and error
|
||||||
|
// interfaces to be used for pretty printing ordinarily unaddressable and
|
||||||
|
// inaccessible values such as unexported struct fields.
|
||||||
|
func unsafeReflectValue(v reflect.Value) (rv reflect.Value) {
|
||||||
|
indirects := 1
|
||||||
|
vt := v.Type()
|
||||||
|
upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr)
|
||||||
|
rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag))
|
||||||
|
if rvf&flagIndir != 0 {
|
||||||
|
vt = reflect.PtrTo(v.Type())
|
||||||
|
indirects++
|
||||||
|
} else if offsetScalar != 0 {
|
||||||
|
// The value is in the scalar field when it's not one of the
|
||||||
|
// reference types.
|
||||||
|
switch vt.Kind() {
|
||||||
|
case reflect.Uintptr:
|
||||||
|
case reflect.Chan:
|
||||||
|
case reflect.Func:
|
||||||
|
case reflect.Map:
|
||||||
|
case reflect.Ptr:
|
||||||
|
case reflect.UnsafePointer:
|
||||||
|
default:
|
||||||
|
upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) +
|
||||||
|
offsetScalar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pv := reflect.NewAt(vt, upv)
|
||||||
|
rv = pv
|
||||||
|
for i := 0; i < indirects; i++ {
|
||||||
|
rv = rv.Elem()
|
||||||
|
}
|
||||||
|
return rv
|
||||||
|
}
|
||||||
37
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
generated
vendored
Normal file
37
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright (c) 2015 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when either the code is running on Google App Engine or "-tags disableunsafe"
|
||||||
|
// is added to the go build command line.
|
||||||
|
// +build appengine disableunsafe
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||||
|
// not access to the unsafe package is available.
|
||||||
|
UnsafeDisabled = true
|
||||||
|
)
|
||||||
|
|
||||||
|
// unsafeReflectValue typically converts the passed reflect.Value into a one
|
||||||
|
// that bypasses the typical safety restrictions preventing access to
|
||||||
|
// unaddressable and unexported data. However, doing this relies on access to
|
||||||
|
// the unsafe package. This is a stub version which simply returns the passed
|
||||||
|
// reflect.Value when the unsafe package is not available.
|
||||||
|
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||||
|
return v
|
||||||
|
}
|
||||||
341
vendor/github.com/davecgh/go-spew/spew/common.go
generated
vendored
Normal file
341
vendor/github.com/davecgh/go-spew/spew/common.go
generated
vendored
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Some constants in the form of bytes to avoid string overhead. This mirrors
|
||||||
|
// the technique used in the fmt package.
|
||||||
|
var (
|
||||||
|
panicBytes = []byte("(PANIC=")
|
||||||
|
plusBytes = []byte("+")
|
||||||
|
iBytes = []byte("i")
|
||||||
|
trueBytes = []byte("true")
|
||||||
|
falseBytes = []byte("false")
|
||||||
|
interfaceBytes = []byte("(interface {})")
|
||||||
|
commaNewlineBytes = []byte(",\n")
|
||||||
|
newlineBytes = []byte("\n")
|
||||||
|
openBraceBytes = []byte("{")
|
||||||
|
openBraceNewlineBytes = []byte("{\n")
|
||||||
|
closeBraceBytes = []byte("}")
|
||||||
|
asteriskBytes = []byte("*")
|
||||||
|
colonBytes = []byte(":")
|
||||||
|
colonSpaceBytes = []byte(": ")
|
||||||
|
openParenBytes = []byte("(")
|
||||||
|
closeParenBytes = []byte(")")
|
||||||
|
spaceBytes = []byte(" ")
|
||||||
|
pointerChainBytes = []byte("->")
|
||||||
|
nilAngleBytes = []byte("<nil>")
|
||||||
|
maxNewlineBytes = []byte("<max depth reached>\n")
|
||||||
|
maxShortBytes = []byte("<max>")
|
||||||
|
circularBytes = []byte("<already shown>")
|
||||||
|
circularShortBytes = []byte("<shown>")
|
||||||
|
invalidAngleBytes = []byte("<invalid>")
|
||||||
|
openBracketBytes = []byte("[")
|
||||||
|
closeBracketBytes = []byte("]")
|
||||||
|
percentBytes = []byte("%")
|
||||||
|
precisionBytes = []byte(".")
|
||||||
|
openAngleBytes = []byte("<")
|
||||||
|
closeAngleBytes = []byte(">")
|
||||||
|
openMapBytes = []byte("map[")
|
||||||
|
closeMapBytes = []byte("]")
|
||||||
|
lenEqualsBytes = []byte("len=")
|
||||||
|
capEqualsBytes = []byte("cap=")
|
||||||
|
)
|
||||||
|
|
||||||
|
// hexDigits is used to map a decimal value to a hex digit.
|
||||||
|
var hexDigits = "0123456789abcdef"
|
||||||
|
|
||||||
|
// catchPanic handles any panics that might occur during the handleMethods
|
||||||
|
// calls.
|
||||||
|
func catchPanic(w io.Writer, v reflect.Value) {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
w.Write(panicBytes)
|
||||||
|
fmt.Fprintf(w, "%v", err)
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleMethods attempts to call the Error and String methods on the underlying
|
||||||
|
// type the passed reflect.Value represents and outputes the result to Writer w.
|
||||||
|
//
|
||||||
|
// It handles panics in any called methods by catching and displaying the error
|
||||||
|
// as the formatted value.
|
||||||
|
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
|
||||||
|
// We need an interface to check if the type implements the error or
|
||||||
|
// Stringer interface. However, the reflect package won't give us an
|
||||||
|
// interface on certain things like unexported struct fields in order
|
||||||
|
// to enforce visibility rules. We use unsafe, when it's available,
|
||||||
|
// to bypass these restrictions since this package does not mutate the
|
||||||
|
// values.
|
||||||
|
if !v.CanInterface() {
|
||||||
|
if UnsafeDisabled {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
v = unsafeReflectValue(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choose whether or not to do error and Stringer interface lookups against
|
||||||
|
// the base type or a pointer to the base type depending on settings.
|
||||||
|
// Technically calling one of these methods with a pointer receiver can
|
||||||
|
// mutate the value, however, types which choose to satisify an error or
|
||||||
|
// Stringer interface with a pointer receiver should not be mutating their
|
||||||
|
// state inside these interface methods.
|
||||||
|
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
|
||||||
|
v = unsafeReflectValue(v)
|
||||||
|
}
|
||||||
|
if v.CanAddr() {
|
||||||
|
v = v.Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is it an error or Stringer?
|
||||||
|
switch iface := v.Interface().(type) {
|
||||||
|
case error:
|
||||||
|
defer catchPanic(w, v)
|
||||||
|
if cs.ContinueOnMethod {
|
||||||
|
w.Write(openParenBytes)
|
||||||
|
w.Write([]byte(iface.Error()))
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
w.Write(spaceBytes)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(iface.Error()))
|
||||||
|
return true
|
||||||
|
|
||||||
|
case fmt.Stringer:
|
||||||
|
defer catchPanic(w, v)
|
||||||
|
if cs.ContinueOnMethod {
|
||||||
|
w.Write(openParenBytes)
|
||||||
|
w.Write([]byte(iface.String()))
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
w.Write(spaceBytes)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
w.Write([]byte(iface.String()))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// printBool outputs a boolean value as true or false to Writer w.
|
||||||
|
func printBool(w io.Writer, val bool) {
|
||||||
|
if val {
|
||||||
|
w.Write(trueBytes)
|
||||||
|
} else {
|
||||||
|
w.Write(falseBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// printInt outputs a signed integer value to Writer w.
|
||||||
|
func printInt(w io.Writer, val int64, base int) {
|
||||||
|
w.Write([]byte(strconv.FormatInt(val, base)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// printUint outputs an unsigned integer value to Writer w.
|
||||||
|
func printUint(w io.Writer, val uint64, base int) {
|
||||||
|
w.Write([]byte(strconv.FormatUint(val, base)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// printFloat outputs a floating point value using the specified precision,
|
||||||
|
// which is expected to be 32 or 64bit, to Writer w.
|
||||||
|
func printFloat(w io.Writer, val float64, precision int) {
|
||||||
|
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// printComplex outputs a complex value using the specified float precision
|
||||||
|
// for the real and imaginary parts to Writer w.
|
||||||
|
func printComplex(w io.Writer, c complex128, floatPrecision int) {
|
||||||
|
r := real(c)
|
||||||
|
w.Write(openParenBytes)
|
||||||
|
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
|
||||||
|
i := imag(c)
|
||||||
|
if i >= 0 {
|
||||||
|
w.Write(plusBytes)
|
||||||
|
}
|
||||||
|
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
|
||||||
|
w.Write(iBytes)
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x'
|
||||||
|
// prefix to Writer w.
|
||||||
|
func printHexPtr(w io.Writer, p uintptr) {
|
||||||
|
// Null pointer.
|
||||||
|
num := uint64(p)
|
||||||
|
if num == 0 {
|
||||||
|
w.Write(nilAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
|
||||||
|
buf := make([]byte, 18)
|
||||||
|
|
||||||
|
// It's simpler to construct the hex string right to left.
|
||||||
|
base := uint64(16)
|
||||||
|
i := len(buf) - 1
|
||||||
|
for num >= base {
|
||||||
|
buf[i] = hexDigits[num%base]
|
||||||
|
num /= base
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
buf[i] = hexDigits[num]
|
||||||
|
|
||||||
|
// Add '0x' prefix.
|
||||||
|
i--
|
||||||
|
buf[i] = 'x'
|
||||||
|
i--
|
||||||
|
buf[i] = '0'
|
||||||
|
|
||||||
|
// Strip unused leading bytes.
|
||||||
|
buf = buf[i:]
|
||||||
|
w.Write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
||||||
|
// elements to be sorted.
|
||||||
|
type valuesSorter struct {
|
||||||
|
values []reflect.Value
|
||||||
|
strings []string // either nil or same len and values
|
||||||
|
cs *ConfigState
|
||||||
|
}
|
||||||
|
|
||||||
|
// newValuesSorter initializes a valuesSorter instance, which holds a set of
|
||||||
|
// surrogate keys on which the data should be sorted. It uses flags in
|
||||||
|
// ConfigState to decide if and how to populate those surrogate keys.
|
||||||
|
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
|
||||||
|
vs := &valuesSorter{values: values, cs: cs}
|
||||||
|
if canSortSimply(vs.values[0].Kind()) {
|
||||||
|
return vs
|
||||||
|
}
|
||||||
|
if !cs.DisableMethods {
|
||||||
|
vs.strings = make([]string, len(values))
|
||||||
|
for i := range vs.values {
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
if !handleMethods(cs, &b, vs.values[i]) {
|
||||||
|
vs.strings = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
vs.strings[i] = b.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if vs.strings == nil && cs.SpewKeys {
|
||||||
|
vs.strings = make([]string, len(values))
|
||||||
|
for i := range vs.values {
|
||||||
|
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vs
|
||||||
|
}
|
||||||
|
|
||||||
|
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
|
||||||
|
// directly, or whether it should be considered for sorting by surrogate keys
|
||||||
|
// (if the ConfigState allows it).
|
||||||
|
func canSortSimply(kind reflect.Kind) bool {
|
||||||
|
// This switch parallels valueSortLess, except for the default case.
|
||||||
|
switch kind {
|
||||||
|
case reflect.Bool:
|
||||||
|
return true
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
return true
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
return true
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return true
|
||||||
|
case reflect.String:
|
||||||
|
return true
|
||||||
|
case reflect.Uintptr:
|
||||||
|
return true
|
||||||
|
case reflect.Array:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of values in the slice. It is part of the
|
||||||
|
// sort.Interface implementation.
|
||||||
|
func (s *valuesSorter) Len() int {
|
||||||
|
return len(s.values)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap swaps the values at the passed indices. It is part of the
|
||||||
|
// sort.Interface implementation.
|
||||||
|
func (s *valuesSorter) Swap(i, j int) {
|
||||||
|
s.values[i], s.values[j] = s.values[j], s.values[i]
|
||||||
|
if s.strings != nil {
|
||||||
|
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// valueSortLess returns whether the first value should sort before the second
|
||||||
|
// value. It is used by valueSorter.Less as part of the sort.Interface
|
||||||
|
// implementation.
|
||||||
|
func valueSortLess(a, b reflect.Value) bool {
|
||||||
|
switch a.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return !a.Bool() && b.Bool()
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
return a.Int() < b.Int()
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
return a.Uint() < b.Uint()
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return a.Float() < b.Float()
|
||||||
|
case reflect.String:
|
||||||
|
return a.String() < b.String()
|
||||||
|
case reflect.Uintptr:
|
||||||
|
return a.Uint() < b.Uint()
|
||||||
|
case reflect.Array:
|
||||||
|
// Compare the contents of both arrays.
|
||||||
|
l := a.Len()
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
av := a.Index(i)
|
||||||
|
bv := b.Index(i)
|
||||||
|
if av.Interface() == bv.Interface() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return valueSortLess(av, bv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a.String() < b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less returns whether the value at index i should sort before the
|
||||||
|
// value at index j. It is part of the sort.Interface implementation.
|
||||||
|
func (s *valuesSorter) Less(i, j int) bool {
|
||||||
|
if s.strings == nil {
|
||||||
|
return valueSortLess(s.values[i], s.values[j])
|
||||||
|
}
|
||||||
|
return s.strings[i] < s.strings[j]
|
||||||
|
}
|
||||||
|
|
||||||
|
// sortValues is a sort function that handles both native types and any type that
|
||||||
|
// can be converted to error or Stringer. Other inputs are sorted according to
|
||||||
|
// their Value.String() value to ensure display stability.
|
||||||
|
func sortValues(values []reflect.Value, cs *ConfigState) {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sort.Sort(newValuesSorter(values, cs))
|
||||||
|
}
|
||||||
298
vendor/github.com/davecgh/go-spew/spew/common_test.go
generated
vendored
Normal file
298
vendor/github.com/davecgh/go-spew/spew/common_test.go
generated
vendored
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// custom type to test Stinger interface on non-pointer receiver.
|
||||||
|
type stringer string
|
||||||
|
|
||||||
|
// String implements the Stringer interface for testing invocation of custom
|
||||||
|
// stringers on types with non-pointer receivers.
|
||||||
|
func (s stringer) String() string {
|
||||||
|
return "stringer " + string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// custom type to test Stinger interface on pointer receiver.
|
||||||
|
type pstringer string
|
||||||
|
|
||||||
|
// String implements the Stringer interface for testing invocation of custom
|
||||||
|
// stringers on types with only pointer receivers.
|
||||||
|
func (s *pstringer) String() string {
|
||||||
|
return "stringer " + string(*s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// xref1 and xref2 are cross referencing structs for testing circular reference
|
||||||
|
// detection.
|
||||||
|
type xref1 struct {
|
||||||
|
ps2 *xref2
|
||||||
|
}
|
||||||
|
type xref2 struct {
|
||||||
|
ps1 *xref1
|
||||||
|
}
|
||||||
|
|
||||||
|
// indirCir1, indirCir2, and indirCir3 are used to generate an indirect circular
|
||||||
|
// reference for testing detection.
|
||||||
|
type indirCir1 struct {
|
||||||
|
ps2 *indirCir2
|
||||||
|
}
|
||||||
|
type indirCir2 struct {
|
||||||
|
ps3 *indirCir3
|
||||||
|
}
|
||||||
|
type indirCir3 struct {
|
||||||
|
ps1 *indirCir1
|
||||||
|
}
|
||||||
|
|
||||||
|
// embed is used to test embedded structures.
|
||||||
|
type embed struct {
|
||||||
|
a string
|
||||||
|
}
|
||||||
|
|
||||||
|
// embedwrap is used to test embedded structures.
|
||||||
|
type embedwrap struct {
|
||||||
|
*embed
|
||||||
|
e *embed
|
||||||
|
}
|
||||||
|
|
||||||
|
// panicer is used to intentionally cause a panic for testing spew properly
|
||||||
|
// handles them
|
||||||
|
type panicer int
|
||||||
|
|
||||||
|
func (p panicer) String() string {
|
||||||
|
panic("test panic")
|
||||||
|
}
|
||||||
|
|
||||||
|
// customError is used to test custom error interface invocation.
|
||||||
|
type customError int
|
||||||
|
|
||||||
|
func (e customError) Error() string {
|
||||||
|
return fmt.Sprintf("error: %d", int(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringizeWants converts a slice of wanted test output into a format suitable
|
||||||
|
// for a test error message.
|
||||||
|
func stringizeWants(wants []string) string {
|
||||||
|
s := ""
|
||||||
|
for i, want := range wants {
|
||||||
|
if i > 0 {
|
||||||
|
s += fmt.Sprintf("want%d: %s", i+1, want)
|
||||||
|
} else {
|
||||||
|
s += "want: " + want
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// testFailed returns whether or not a test failed by checking if the result
|
||||||
|
// of the test is in the slice of wanted strings.
|
||||||
|
func testFailed(result string, wants []string) bool {
|
||||||
|
for _, want := range wants {
|
||||||
|
if result == want {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortableStruct struct {
|
||||||
|
x int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss sortableStruct) String() string {
|
||||||
|
return fmt.Sprintf("ss.%d", ss.x)
|
||||||
|
}
|
||||||
|
|
||||||
|
type unsortableStruct struct {
|
||||||
|
x int
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortTestCase struct {
|
||||||
|
input []reflect.Value
|
||||||
|
expected []reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func helpTestSortValues(tests []sortTestCase, cs *spew.ConfigState, t *testing.T) {
|
||||||
|
getInterfaces := func(values []reflect.Value) []interface{} {
|
||||||
|
interfaces := []interface{}{}
|
||||||
|
for _, v := range values {
|
||||||
|
interfaces = append(interfaces, v.Interface())
|
||||||
|
}
|
||||||
|
return interfaces
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
spew.SortValues(test.input, cs)
|
||||||
|
// reflect.DeepEqual cannot really make sense of reflect.Value,
|
||||||
|
// probably because of all the pointer tricks. For instance,
|
||||||
|
// v(2.0) != v(2.0) on a 32-bits system. Turn them into interface{}
|
||||||
|
// instead.
|
||||||
|
input := getInterfaces(test.input)
|
||||||
|
expected := getInterfaces(test.expected)
|
||||||
|
if !reflect.DeepEqual(input, expected) {
|
||||||
|
t.Errorf("Sort mismatch:\n %v != %v", input, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSortValues ensures the sort functionality for relect.Value based sorting
|
||||||
|
// works as intended.
|
||||||
|
func TestSortValues(t *testing.T) {
|
||||||
|
v := reflect.ValueOf
|
||||||
|
|
||||||
|
a := v("a")
|
||||||
|
b := v("b")
|
||||||
|
c := v("c")
|
||||||
|
embedA := v(embed{"a"})
|
||||||
|
embedB := v(embed{"b"})
|
||||||
|
embedC := v(embed{"c"})
|
||||||
|
tests := []sortTestCase{
|
||||||
|
// No values.
|
||||||
|
{
|
||||||
|
[]reflect.Value{},
|
||||||
|
[]reflect.Value{},
|
||||||
|
},
|
||||||
|
// Bools.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(false), v(true), v(false)},
|
||||||
|
[]reflect.Value{v(false), v(false), v(true)},
|
||||||
|
},
|
||||||
|
// Ints.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(2), v(1), v(3)},
|
||||||
|
[]reflect.Value{v(1), v(2), v(3)},
|
||||||
|
},
|
||||||
|
// Uints.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(uint8(2)), v(uint8(1)), v(uint8(3))},
|
||||||
|
[]reflect.Value{v(uint8(1)), v(uint8(2)), v(uint8(3))},
|
||||||
|
},
|
||||||
|
// Floats.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(2.0), v(1.0), v(3.0)},
|
||||||
|
[]reflect.Value{v(1.0), v(2.0), v(3.0)},
|
||||||
|
},
|
||||||
|
// Strings.
|
||||||
|
{
|
||||||
|
[]reflect.Value{b, a, c},
|
||||||
|
[]reflect.Value{a, b, c},
|
||||||
|
},
|
||||||
|
// Array
|
||||||
|
{
|
||||||
|
[]reflect.Value{v([3]int{3, 2, 1}), v([3]int{1, 3, 2}), v([3]int{1, 2, 3})},
|
||||||
|
[]reflect.Value{v([3]int{1, 2, 3}), v([3]int{1, 3, 2}), v([3]int{3, 2, 1})},
|
||||||
|
},
|
||||||
|
// Uintptrs.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(uintptr(2)), v(uintptr(1)), v(uintptr(3))},
|
||||||
|
[]reflect.Value{v(uintptr(1)), v(uintptr(2)), v(uintptr(3))},
|
||||||
|
},
|
||||||
|
// SortableStructs.
|
||||||
|
{
|
||||||
|
// Note: not sorted - DisableMethods is set.
|
||||||
|
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||||
|
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||||
|
},
|
||||||
|
// UnsortableStructs.
|
||||||
|
{
|
||||||
|
// Note: not sorted - SpewKeys is false.
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
},
|
||||||
|
// Invalid.
|
||||||
|
{
|
||||||
|
[]reflect.Value{embedB, embedA, embedC},
|
||||||
|
[]reflect.Value{embedB, embedA, embedC},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cs := spew.ConfigState{DisableMethods: true, SpewKeys: false}
|
||||||
|
helpTestSortValues(tests, &cs, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSortValuesWithMethods ensures the sort functionality for relect.Value
|
||||||
|
// based sorting works as intended when using string methods.
|
||||||
|
func TestSortValuesWithMethods(t *testing.T) {
|
||||||
|
v := reflect.ValueOf
|
||||||
|
|
||||||
|
a := v("a")
|
||||||
|
b := v("b")
|
||||||
|
c := v("c")
|
||||||
|
tests := []sortTestCase{
|
||||||
|
// Ints.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(2), v(1), v(3)},
|
||||||
|
[]reflect.Value{v(1), v(2), v(3)},
|
||||||
|
},
|
||||||
|
// Strings.
|
||||||
|
{
|
||||||
|
[]reflect.Value{b, a, c},
|
||||||
|
[]reflect.Value{a, b, c},
|
||||||
|
},
|
||||||
|
// SortableStructs.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||||
|
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
|
||||||
|
},
|
||||||
|
// UnsortableStructs.
|
||||||
|
{
|
||||||
|
// Note: not sorted - SpewKeys is false.
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cs := spew.ConfigState{DisableMethods: false, SpewKeys: false}
|
||||||
|
helpTestSortValues(tests, &cs, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSortValuesWithSpew ensures the sort functionality for relect.Value
|
||||||
|
// based sorting works as intended when using spew to stringify keys.
|
||||||
|
func TestSortValuesWithSpew(t *testing.T) {
|
||||||
|
v := reflect.ValueOf
|
||||||
|
|
||||||
|
a := v("a")
|
||||||
|
b := v("b")
|
||||||
|
c := v("c")
|
||||||
|
tests := []sortTestCase{
|
||||||
|
// Ints.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(2), v(1), v(3)},
|
||||||
|
[]reflect.Value{v(1), v(2), v(3)},
|
||||||
|
},
|
||||||
|
// Strings.
|
||||||
|
{
|
||||||
|
[]reflect.Value{b, a, c},
|
||||||
|
[]reflect.Value{a, b, c},
|
||||||
|
},
|
||||||
|
// SortableStructs.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||||
|
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
|
||||||
|
},
|
||||||
|
// UnsortableStructs.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
[]reflect.Value{v(unsortableStruct{1}), v(unsortableStruct{2}), v(unsortableStruct{3})},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cs := spew.ConfigState{DisableMethods: true, SpewKeys: true}
|
||||||
|
helpTestSortValues(tests, &cs, t)
|
||||||
|
}
|
||||||
297
vendor/github.com/davecgh/go-spew/spew/config.go
generated
vendored
Normal file
297
vendor/github.com/davecgh/go-spew/spew/config.go
generated
vendored
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigState houses the configuration options used by spew to format and
|
||||||
|
// display values. There is a global instance, Config, that is used to control
|
||||||
|
// all top-level Formatter and Dump functionality. Each ConfigState instance
|
||||||
|
// provides methods equivalent to the top-level functions.
|
||||||
|
//
|
||||||
|
// The zero value for ConfigState provides no indentation. You would typically
|
||||||
|
// want to set it to a space or a tab.
|
||||||
|
//
|
||||||
|
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
|
||||||
|
// with default settings. See the documentation of NewDefaultConfig for default
|
||||||
|
// values.
|
||||||
|
type ConfigState struct {
|
||||||
|
// Indent specifies the string to use for each indentation level. The
|
||||||
|
// global config instance that all top-level functions use set this to a
|
||||||
|
// single space by default. If you would like more indentation, you might
|
||||||
|
// set this to a tab with "\t" or perhaps two spaces with " ".
|
||||||
|
Indent string
|
||||||
|
|
||||||
|
// MaxDepth controls the maximum number of levels to descend into nested
|
||||||
|
// data structures. The default, 0, means there is no limit.
|
||||||
|
//
|
||||||
|
// NOTE: Circular data structures are properly detected, so it is not
|
||||||
|
// necessary to set this value unless you specifically want to limit deeply
|
||||||
|
// nested data structures.
|
||||||
|
MaxDepth int
|
||||||
|
|
||||||
|
// DisableMethods specifies whether or not error and Stringer interfaces are
|
||||||
|
// invoked for types that implement them.
|
||||||
|
DisableMethods bool
|
||||||
|
|
||||||
|
// DisablePointerMethods specifies whether or not to check for and invoke
|
||||||
|
// error and Stringer interfaces on types which only accept a pointer
|
||||||
|
// receiver when the current type is not a pointer.
|
||||||
|
//
|
||||||
|
// NOTE: This might be an unsafe action since calling one of these methods
|
||||||
|
// with a pointer receiver could technically mutate the value, however,
|
||||||
|
// in practice, types which choose to satisify an error or Stringer
|
||||||
|
// interface with a pointer receiver should not be mutating their state
|
||||||
|
// inside these interface methods. As a result, this option relies on
|
||||||
|
// access to the unsafe package, so it will not have any effect when
|
||||||
|
// running in environments without access to the unsafe package such as
|
||||||
|
// Google App Engine or with the "disableunsafe" build tag specified.
|
||||||
|
DisablePointerMethods bool
|
||||||
|
|
||||||
|
// ContinueOnMethod specifies whether or not recursion should continue once
|
||||||
|
// a custom error or Stringer interface is invoked. The default, false,
|
||||||
|
// means it will print the results of invoking the custom error or Stringer
|
||||||
|
// interface and return immediately instead of continuing to recurse into
|
||||||
|
// the internals of the data type.
|
||||||
|
//
|
||||||
|
// NOTE: This flag does not have any effect if method invocation is disabled
|
||||||
|
// via the DisableMethods or DisablePointerMethods options.
|
||||||
|
ContinueOnMethod bool
|
||||||
|
|
||||||
|
// SortKeys specifies map keys should be sorted before being printed. Use
|
||||||
|
// this to have a more deterministic, diffable output. Note that only
|
||||||
|
// native types (bool, int, uint, floats, uintptr and string) and types
|
||||||
|
// that support the error or Stringer interfaces (if methods are
|
||||||
|
// enabled) are supported, with other types sorted according to the
|
||||||
|
// reflect.Value.String() output which guarantees display stability.
|
||||||
|
SortKeys bool
|
||||||
|
|
||||||
|
// SpewKeys specifies that, as a last resort attempt, map keys should
|
||||||
|
// be spewed to strings and sorted by those strings. This is only
|
||||||
|
// considered if SortKeys is true.
|
||||||
|
SpewKeys bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is the active configuration of the top-level functions.
|
||||||
|
// The configuration can be changed by modifying the contents of spew.Config.
|
||||||
|
var Config = ConfigState{Indent: " "}
|
||||||
|
|
||||||
|
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the formatted string as a value that satisfies error. See NewFormatter
|
||||||
|
// for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
|
||||||
|
return fmt.Errorf(format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprint(w, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintf(w, format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintln(w, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Print(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Printf(format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Println(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Sprint(a ...interface{}) string {
|
||||||
|
return fmt.Sprint(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
|
||||||
|
return fmt.Sprintf(format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||||
|
// were passed with a Formatter interface returned by c.NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Sprintln(a ...interface{}) string {
|
||||||
|
return fmt.Sprintln(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||||
|
interface. As a result, it integrates cleanly with standard fmt package
|
||||||
|
printing functions. The formatter is useful for inline printing of smaller data
|
||||||
|
types similar to the standard %v format specifier.
|
||||||
|
|
||||||
|
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||||
|
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
|
||||||
|
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||||
|
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||||
|
the width and precision arguments (however they will still work on the format
|
||||||
|
specifiers not handled by the custom formatter).
|
||||||
|
|
||||||
|
Typically this function shouldn't be called directly. It is much easier to make
|
||||||
|
use of the custom formatter by calling one of the convenience functions such as
|
||||||
|
c.Printf, c.Println, or c.Printf.
|
||||||
|
*/
|
||||||
|
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
|
||||||
|
return newFormatter(c, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||||
|
// exactly the same as Dump.
|
||||||
|
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
|
||||||
|
fdump(c, w, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Dump displays the passed parameters to standard out with newlines, customizable
|
||||||
|
indentation, and additional debug information such as complete types and all
|
||||||
|
pointer addresses used to indirect to the final value. It provides the
|
||||||
|
following features over the built-in printing facilities provided by the fmt
|
||||||
|
package:
|
||||||
|
|
||||||
|
* Pointers are dereferenced and followed
|
||||||
|
* Circular data structures are detected and handled properly
|
||||||
|
* Custom Stringer/error interfaces are optionally invoked, including
|
||||||
|
on unexported types
|
||||||
|
* Custom types which only implement the Stringer/error interfaces via
|
||||||
|
a pointer receiver are optionally invoked when passing non-pointer
|
||||||
|
variables
|
||||||
|
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||||
|
includes offsets, byte values in hex, and ASCII output
|
||||||
|
|
||||||
|
The configuration options are controlled by modifying the public members
|
||||||
|
of c. See ConfigState for options documentation.
|
||||||
|
|
||||||
|
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||||
|
get the formatted result as a string.
|
||||||
|
*/
|
||||||
|
func (c *ConfigState) Dump(a ...interface{}) {
|
||||||
|
fdump(c, os.Stdout, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||||
|
// as Dump.
|
||||||
|
func (c *ConfigState) Sdump(a ...interface{}) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fdump(c, &buf, a...)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||||
|
// length with each argument converted to a spew Formatter interface using
|
||||||
|
// the ConfigState associated with s.
|
||||||
|
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
|
||||||
|
formatters = make([]interface{}, len(args))
|
||||||
|
for index, arg := range args {
|
||||||
|
formatters[index] = newFormatter(c, arg)
|
||||||
|
}
|
||||||
|
return formatters
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultConfig returns a ConfigState with the following default settings.
|
||||||
|
//
|
||||||
|
// Indent: " "
|
||||||
|
// MaxDepth: 0
|
||||||
|
// DisableMethods: false
|
||||||
|
// DisablePointerMethods: false
|
||||||
|
// ContinueOnMethod: false
|
||||||
|
// SortKeys: false
|
||||||
|
func NewDefaultConfig() *ConfigState {
|
||||||
|
return &ConfigState{Indent: " "}
|
||||||
|
}
|
||||||
202
vendor/github.com/davecgh/go-spew/spew/doc.go
generated
vendored
Normal file
202
vendor/github.com/davecgh/go-spew/spew/doc.go
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package spew implements a deep pretty printer for Go data structures to aid in
|
||||||
|
debugging.
|
||||||
|
|
||||||
|
A quick overview of the additional features spew provides over the built-in
|
||||||
|
printing facilities for Go data types are as follows:
|
||||||
|
|
||||||
|
* Pointers are dereferenced and followed
|
||||||
|
* Circular data structures are detected and handled properly
|
||||||
|
* Custom Stringer/error interfaces are optionally invoked, including
|
||||||
|
on unexported types
|
||||||
|
* Custom types which only implement the Stringer/error interfaces via
|
||||||
|
a pointer receiver are optionally invoked when passing non-pointer
|
||||||
|
variables
|
||||||
|
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||||
|
includes offsets, byte values in hex, and ASCII output (only when using
|
||||||
|
Dump style)
|
||||||
|
|
||||||
|
There are two different approaches spew allows for dumping Go data structures:
|
||||||
|
|
||||||
|
* Dump style which prints with newlines, customizable indentation,
|
||||||
|
and additional debug information such as types and all pointer addresses
|
||||||
|
used to indirect to the final value
|
||||||
|
* A custom Formatter interface that integrates cleanly with the standard fmt
|
||||||
|
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
|
||||||
|
similar to the default %v while providing the additional functionality
|
||||||
|
outlined above and passing unsupported format verbs such as %x and %q
|
||||||
|
along to fmt
|
||||||
|
|
||||||
|
Quick Start
|
||||||
|
|
||||||
|
This section demonstrates how to quickly get started with spew. See the
|
||||||
|
sections below for further details on formatting and configuration options.
|
||||||
|
|
||||||
|
To dump a variable with full newlines, indentation, type, and pointer
|
||||||
|
information use Dump, Fdump, or Sdump:
|
||||||
|
spew.Dump(myVar1, myVar2, ...)
|
||||||
|
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
||||||
|
str := spew.Sdump(myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
Alternatively, if you would prefer to use format strings with a compacted inline
|
||||||
|
printing style, use the convenience wrappers Printf, Fprintf, etc with
|
||||||
|
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
|
||||||
|
%#+v (adds types and pointer addresses):
|
||||||
|
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
|
||||||
|
Configuration Options
|
||||||
|
|
||||||
|
Configuration of spew is handled by fields in the ConfigState type. For
|
||||||
|
convenience, all of the top-level functions use a global state available
|
||||||
|
via the spew.Config global.
|
||||||
|
|
||||||
|
It is also possible to create a ConfigState instance that provides methods
|
||||||
|
equivalent to the top-level functions. This allows concurrent configuration
|
||||||
|
options. See the ConfigState documentation for more details.
|
||||||
|
|
||||||
|
The following configuration options are available:
|
||||||
|
* Indent
|
||||||
|
String to use for each indentation level for Dump functions.
|
||||||
|
It is a single space by default. A popular alternative is "\t".
|
||||||
|
|
||||||
|
* MaxDepth
|
||||||
|
Maximum number of levels to descend into nested data structures.
|
||||||
|
There is no limit by default.
|
||||||
|
|
||||||
|
* DisableMethods
|
||||||
|
Disables invocation of error and Stringer interface methods.
|
||||||
|
Method invocation is enabled by default.
|
||||||
|
|
||||||
|
* DisablePointerMethods
|
||||||
|
Disables invocation of error and Stringer interface methods on types
|
||||||
|
which only accept pointer receivers from non-pointer variables.
|
||||||
|
Pointer method invocation is enabled by default.
|
||||||
|
|
||||||
|
* ContinueOnMethod
|
||||||
|
Enables recursion into types after invoking error and Stringer interface
|
||||||
|
methods. Recursion after method invocation is disabled by default.
|
||||||
|
|
||||||
|
* SortKeys
|
||||||
|
Specifies map keys should be sorted before being printed. Use
|
||||||
|
this to have a more deterministic, diffable output. Note that
|
||||||
|
only native types (bool, int, uint, floats, uintptr and string)
|
||||||
|
and types which implement error or Stringer interfaces are
|
||||||
|
supported with other types sorted according to the
|
||||||
|
reflect.Value.String() output which guarantees display
|
||||||
|
stability. Natural map order is used by default.
|
||||||
|
|
||||||
|
* SpewKeys
|
||||||
|
Specifies that, as a last resort attempt, map keys should be
|
||||||
|
spewed to strings and sorted by those strings. This is only
|
||||||
|
considered if SortKeys is true.
|
||||||
|
|
||||||
|
Dump Usage
|
||||||
|
|
||||||
|
Simply call spew.Dump with a list of variables you want to dump:
|
||||||
|
|
||||||
|
spew.Dump(myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
You may also call spew.Fdump if you would prefer to output to an arbitrary
|
||||||
|
io.Writer. For example, to dump to standard error:
|
||||||
|
|
||||||
|
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
A third option is to call spew.Sdump to get the formatted output as a string:
|
||||||
|
|
||||||
|
str := spew.Sdump(myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
Sample Dump Output
|
||||||
|
|
||||||
|
See the Dump example for details on the setup of the types and variables being
|
||||||
|
shown here.
|
||||||
|
|
||||||
|
(main.Foo) {
|
||||||
|
unexportedField: (*main.Bar)(0xf84002e210)({
|
||||||
|
flag: (main.Flag) flagTwo,
|
||||||
|
data: (uintptr) <nil>
|
||||||
|
}),
|
||||||
|
ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||||
|
(string) (len=3) "one": (bool) true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
|
||||||
|
command as shown.
|
||||||
|
([]uint8) (len=32 cap=32) {
|
||||||
|
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||||
|
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||||
|
00000020 31 32 |12|
|
||||||
|
}
|
||||||
|
|
||||||
|
Custom Formatter
|
||||||
|
|
||||||
|
Spew provides a custom formatter that implements the fmt.Formatter interface
|
||||||
|
so that it integrates cleanly with standard fmt package printing functions. The
|
||||||
|
formatter is useful for inline printing of smaller data types similar to the
|
||||||
|
standard %v format specifier.
|
||||||
|
|
||||||
|
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||||
|
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||||
|
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||||
|
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||||
|
the width and precision arguments (however they will still work on the format
|
||||||
|
specifiers not handled by the custom formatter).
|
||||||
|
|
||||||
|
Custom Formatter Usage
|
||||||
|
|
||||||
|
The simplest way to make use of the spew custom formatter is to call one of the
|
||||||
|
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
|
||||||
|
functions have syntax you are most likely already familiar with:
|
||||||
|
|
||||||
|
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
spew.Println(myVar, myVar2)
|
||||||
|
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
|
||||||
|
See the Index for the full list convenience functions.
|
||||||
|
|
||||||
|
Sample Formatter Output
|
||||||
|
|
||||||
|
Double pointer to a uint8:
|
||||||
|
%v: <**>5
|
||||||
|
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
||||||
|
%#v: (**uint8)5
|
||||||
|
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
||||||
|
|
||||||
|
Pointer to circular struct with a uint8 field and a pointer to itself:
|
||||||
|
%v: <*>{1 <*><shown>}
|
||||||
|
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
||||||
|
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
||||||
|
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
||||||
|
|
||||||
|
See the Printf example for details on the setup of variables being shown
|
||||||
|
here.
|
||||||
|
|
||||||
|
Errors
|
||||||
|
|
||||||
|
Since it is possible for custom Stringer/error interfaces to panic, spew
|
||||||
|
detects them and handles them internally by printing the panic information
|
||||||
|
inline with the output. Since spew is intended to provide deep pretty printing
|
||||||
|
capabilities on structures, it intentionally does not return any errors.
|
||||||
|
*/
|
||||||
|
package spew
|
||||||
509
vendor/github.com/davecgh/go-spew/spew/dump.go
generated
vendored
Normal file
509
vendor/github.com/davecgh/go-spew/spew/dump.go
generated
vendored
Normal file
@@ -0,0 +1,509 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// uint8Type is a reflect.Type representing a uint8. It is used to
|
||||||
|
// convert cgo types to uint8 slices for hexdumping.
|
||||||
|
uint8Type = reflect.TypeOf(uint8(0))
|
||||||
|
|
||||||
|
// cCharRE is a regular expression that matches a cgo char.
|
||||||
|
// It is used to detect character arrays to hexdump them.
|
||||||
|
cCharRE = regexp.MustCompile("^.*\\._Ctype_char$")
|
||||||
|
|
||||||
|
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
|
||||||
|
// char. It is used to detect unsigned character arrays to hexdump
|
||||||
|
// them.
|
||||||
|
cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$")
|
||||||
|
|
||||||
|
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
|
||||||
|
// It is used to detect uint8_t arrays to hexdump them.
|
||||||
|
cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$")
|
||||||
|
)
|
||||||
|
|
||||||
|
// dumpState contains information about the state of a dump operation.
|
||||||
|
type dumpState struct {
|
||||||
|
w io.Writer
|
||||||
|
depth int
|
||||||
|
pointers map[uintptr]int
|
||||||
|
ignoreNextType bool
|
||||||
|
ignoreNextIndent bool
|
||||||
|
cs *ConfigState
|
||||||
|
}
|
||||||
|
|
||||||
|
// indent performs indentation according to the depth level and cs.Indent
|
||||||
|
// option.
|
||||||
|
func (d *dumpState) indent() {
|
||||||
|
if d.ignoreNextIndent {
|
||||||
|
d.ignoreNextIndent = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpackValue returns values inside of non-nil interfaces when possible.
|
||||||
|
// This is useful for data types like structs, arrays, slices, and maps which
|
||||||
|
// can contain varying types packed inside an interface.
|
||||||
|
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
|
||||||
|
if v.Kind() == reflect.Interface && !v.IsNil() {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// dumpPtr handles formatting of pointers by indirecting them as necessary.
|
||||||
|
func (d *dumpState) dumpPtr(v reflect.Value) {
|
||||||
|
// Remove pointers at or below the current depth from map used to detect
|
||||||
|
// circular refs.
|
||||||
|
for k, depth := range d.pointers {
|
||||||
|
if depth >= d.depth {
|
||||||
|
delete(d.pointers, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep list of all dereferenced pointers to show later.
|
||||||
|
pointerChain := make([]uintptr, 0)
|
||||||
|
|
||||||
|
// Figure out how many levels of indirection there are by dereferencing
|
||||||
|
// pointers and unpacking interfaces down the chain while detecting circular
|
||||||
|
// references.
|
||||||
|
nilFound := false
|
||||||
|
cycleFound := false
|
||||||
|
indirects := 0
|
||||||
|
ve := v
|
||||||
|
for ve.Kind() == reflect.Ptr {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
indirects++
|
||||||
|
addr := ve.Pointer()
|
||||||
|
pointerChain = append(pointerChain, addr)
|
||||||
|
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
|
||||||
|
cycleFound = true
|
||||||
|
indirects--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
d.pointers[addr] = d.depth
|
||||||
|
|
||||||
|
ve = ve.Elem()
|
||||||
|
if ve.Kind() == reflect.Interface {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ve = ve.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display type information.
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||||
|
d.w.Write([]byte(ve.Type().String()))
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
|
||||||
|
// Display pointer information.
|
||||||
|
if len(pointerChain) > 0 {
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
for i, addr := range pointerChain {
|
||||||
|
if i > 0 {
|
||||||
|
d.w.Write(pointerChainBytes)
|
||||||
|
}
|
||||||
|
printHexPtr(d.w, addr)
|
||||||
|
}
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display dereferenced value.
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
switch {
|
||||||
|
case nilFound == true:
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
|
||||||
|
case cycleFound == true:
|
||||||
|
d.w.Write(circularBytes)
|
||||||
|
|
||||||
|
default:
|
||||||
|
d.ignoreNextType = true
|
||||||
|
d.dump(ve)
|
||||||
|
}
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
|
||||||
|
// reflection) arrays and slices are dumped in hexdump -C fashion.
|
||||||
|
func (d *dumpState) dumpSlice(v reflect.Value) {
|
||||||
|
// Determine whether this type should be hex dumped or not. Also,
|
||||||
|
// for types which should be hexdumped, try to use the underlying data
|
||||||
|
// first, then fall back to trying to convert them to a uint8 slice.
|
||||||
|
var buf []uint8
|
||||||
|
doConvert := false
|
||||||
|
doHexDump := false
|
||||||
|
numEntries := v.Len()
|
||||||
|
if numEntries > 0 {
|
||||||
|
vt := v.Index(0).Type()
|
||||||
|
vts := vt.String()
|
||||||
|
switch {
|
||||||
|
// C types that need to be converted.
|
||||||
|
case cCharRE.MatchString(vts):
|
||||||
|
fallthrough
|
||||||
|
case cUnsignedCharRE.MatchString(vts):
|
||||||
|
fallthrough
|
||||||
|
case cUint8tCharRE.MatchString(vts):
|
||||||
|
doConvert = true
|
||||||
|
|
||||||
|
// Try to use existing uint8 slices and fall back to converting
|
||||||
|
// and copying if that fails.
|
||||||
|
case vt.Kind() == reflect.Uint8:
|
||||||
|
// We need an addressable interface to convert the type
|
||||||
|
// to a byte slice. However, the reflect package won't
|
||||||
|
// give us an interface on certain things like
|
||||||
|
// unexported struct fields in order to enforce
|
||||||
|
// visibility rules. We use unsafe, when available, to
|
||||||
|
// bypass these restrictions since this package does not
|
||||||
|
// mutate the values.
|
||||||
|
vs := v
|
||||||
|
if !vs.CanInterface() || !vs.CanAddr() {
|
||||||
|
vs = unsafeReflectValue(vs)
|
||||||
|
}
|
||||||
|
if !UnsafeDisabled {
|
||||||
|
vs = vs.Slice(0, numEntries)
|
||||||
|
|
||||||
|
// Use the existing uint8 slice if it can be
|
||||||
|
// type asserted.
|
||||||
|
iface := vs.Interface()
|
||||||
|
if slice, ok := iface.([]uint8); ok {
|
||||||
|
buf = slice
|
||||||
|
doHexDump = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The underlying data needs to be converted if it can't
|
||||||
|
// be type asserted to a uint8 slice.
|
||||||
|
doConvert = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy and convert the underlying type if needed.
|
||||||
|
if doConvert && vt.ConvertibleTo(uint8Type) {
|
||||||
|
// Convert and copy each element into a uint8 byte
|
||||||
|
// slice.
|
||||||
|
buf = make([]uint8, numEntries)
|
||||||
|
for i := 0; i < numEntries; i++ {
|
||||||
|
vv := v.Index(i)
|
||||||
|
buf[i] = uint8(vv.Convert(uint8Type).Uint())
|
||||||
|
}
|
||||||
|
doHexDump = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hexdump the entire slice as needed.
|
||||||
|
if doHexDump {
|
||||||
|
indent := strings.Repeat(d.cs.Indent, d.depth)
|
||||||
|
str := indent + hex.Dump(buf)
|
||||||
|
str = strings.Replace(str, "\n", "\n"+indent, -1)
|
||||||
|
str = strings.TrimRight(str, d.cs.Indent)
|
||||||
|
d.w.Write([]byte(str))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively call dump for each item.
|
||||||
|
for i := 0; i < numEntries; i++ {
|
||||||
|
d.dump(d.unpackValue(v.Index(i)))
|
||||||
|
if i < (numEntries - 1) {
|
||||||
|
d.w.Write(commaNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dump is the main workhorse for dumping a value. It uses the passed reflect
|
||||||
|
// value to figure out what kind of object we are dealing with and formats it
|
||||||
|
// appropriately. It is a recursive function, however circular data structures
|
||||||
|
// are detected and handled properly.
|
||||||
|
func (d *dumpState) dump(v reflect.Value) {
|
||||||
|
// Handle invalid reflect values immediately.
|
||||||
|
kind := v.Kind()
|
||||||
|
if kind == reflect.Invalid {
|
||||||
|
d.w.Write(invalidAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle pointers specially.
|
||||||
|
if kind == reflect.Ptr {
|
||||||
|
d.indent()
|
||||||
|
d.dumpPtr(v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print type information unless already handled elsewhere.
|
||||||
|
if !d.ignoreNextType {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
d.w.Write([]byte(v.Type().String()))
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
d.w.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
d.ignoreNextType = false
|
||||||
|
|
||||||
|
// Display length and capacity if the built-in len and cap functions
|
||||||
|
// work with the value's kind and the len/cap itself is non-zero.
|
||||||
|
valueLen, valueCap := 0, 0
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Slice, reflect.Chan:
|
||||||
|
valueLen, valueCap = v.Len(), v.Cap()
|
||||||
|
case reflect.Map, reflect.String:
|
||||||
|
valueLen = v.Len()
|
||||||
|
}
|
||||||
|
if valueLen != 0 || valueCap != 0 {
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
if valueLen != 0 {
|
||||||
|
d.w.Write(lenEqualsBytes)
|
||||||
|
printInt(d.w, int64(valueLen), 10)
|
||||||
|
}
|
||||||
|
if valueCap != 0 {
|
||||||
|
if valueLen != 0 {
|
||||||
|
d.w.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
d.w.Write(capEqualsBytes)
|
||||||
|
printInt(d.w, int64(valueCap), 10)
|
||||||
|
}
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
d.w.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call Stringer/error interfaces if they exist and the handle methods flag
|
||||||
|
// is enabled
|
||||||
|
if !d.cs.DisableMethods {
|
||||||
|
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||||
|
if handled := handleMethods(d.cs, d.w, v); handled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.Invalid:
|
||||||
|
// Do nothing. We should never get here since invalid has already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
printBool(d.w, v.Bool())
|
||||||
|
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
printInt(d.w, v.Int(), 10)
|
||||||
|
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
printUint(d.w, v.Uint(), 10)
|
||||||
|
|
||||||
|
case reflect.Float32:
|
||||||
|
printFloat(d.w, v.Float(), 32)
|
||||||
|
|
||||||
|
case reflect.Float64:
|
||||||
|
printFloat(d.w, v.Float(), 64)
|
||||||
|
|
||||||
|
case reflect.Complex64:
|
||||||
|
printComplex(d.w, v.Complex(), 32)
|
||||||
|
|
||||||
|
case reflect.Complex128:
|
||||||
|
printComplex(d.w, v.Complex(), 64)
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.IsNil() {
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case reflect.Array:
|
||||||
|
d.w.Write(openBraceNewlineBytes)
|
||||||
|
d.depth++
|
||||||
|
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(maxNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.dumpSlice(v)
|
||||||
|
}
|
||||||
|
d.depth--
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.String:
|
||||||
|
d.w.Write([]byte(strconv.Quote(v.String())))
|
||||||
|
|
||||||
|
case reflect.Interface:
|
||||||
|
// The only time we should get here is for nil interfaces due to
|
||||||
|
// unpackValue calls.
|
||||||
|
if v.IsNil() {
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
// Do nothing. We should never get here since pointers have already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
// nil maps should be indicated as different than empty maps
|
||||||
|
if v.IsNil() {
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
d.w.Write(openBraceNewlineBytes)
|
||||||
|
d.depth++
|
||||||
|
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(maxNewlineBytes)
|
||||||
|
} else {
|
||||||
|
numEntries := v.Len()
|
||||||
|
keys := v.MapKeys()
|
||||||
|
if d.cs.SortKeys {
|
||||||
|
sortValues(keys, d.cs)
|
||||||
|
}
|
||||||
|
for i, key := range keys {
|
||||||
|
d.dump(d.unpackValue(key))
|
||||||
|
d.w.Write(colonSpaceBytes)
|
||||||
|
d.ignoreNextIndent = true
|
||||||
|
d.dump(d.unpackValue(v.MapIndex(key)))
|
||||||
|
if i < (numEntries - 1) {
|
||||||
|
d.w.Write(commaNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.depth--
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
d.w.Write(openBraceNewlineBytes)
|
||||||
|
d.depth++
|
||||||
|
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(maxNewlineBytes)
|
||||||
|
} else {
|
||||||
|
vt := v.Type()
|
||||||
|
numFields := v.NumField()
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
d.indent()
|
||||||
|
vtf := vt.Field(i)
|
||||||
|
d.w.Write([]byte(vtf.Name))
|
||||||
|
d.w.Write(colonSpaceBytes)
|
||||||
|
d.ignoreNextIndent = true
|
||||||
|
d.dump(d.unpackValue(v.Field(i)))
|
||||||
|
if i < (numFields - 1) {
|
||||||
|
d.w.Write(commaNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.depth--
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.Uintptr:
|
||||||
|
printHexPtr(d.w, uintptr(v.Uint()))
|
||||||
|
|
||||||
|
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||||
|
printHexPtr(d.w, v.Pointer())
|
||||||
|
|
||||||
|
// There were not any other types at the time this code was written, but
|
||||||
|
// fall back to letting the default fmt package handle it in case any new
|
||||||
|
// types are added.
|
||||||
|
default:
|
||||||
|
if v.CanInterface() {
|
||||||
|
fmt.Fprintf(d.w, "%v", v.Interface())
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(d.w, "%v", v.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fdump is a helper function to consolidate the logic from the various public
|
||||||
|
// methods which take varying writers and config states.
|
||||||
|
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
|
||||||
|
for _, arg := range a {
|
||||||
|
if arg == nil {
|
||||||
|
w.Write(interfaceBytes)
|
||||||
|
w.Write(spaceBytes)
|
||||||
|
w.Write(nilAngleBytes)
|
||||||
|
w.Write(newlineBytes)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
d := dumpState{w: w, cs: cs}
|
||||||
|
d.pointers = make(map[uintptr]int)
|
||||||
|
d.dump(reflect.ValueOf(arg))
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||||
|
// exactly the same as Dump.
|
||||||
|
func Fdump(w io.Writer, a ...interface{}) {
|
||||||
|
fdump(&Config, w, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||||
|
// as Dump.
|
||||||
|
func Sdump(a ...interface{}) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fdump(&Config, &buf, a...)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Dump displays the passed parameters to standard out with newlines, customizable
|
||||||
|
indentation, and additional debug information such as complete types and all
|
||||||
|
pointer addresses used to indirect to the final value. It provides the
|
||||||
|
following features over the built-in printing facilities provided by the fmt
|
||||||
|
package:
|
||||||
|
|
||||||
|
* Pointers are dereferenced and followed
|
||||||
|
* Circular data structures are detected and handled properly
|
||||||
|
* Custom Stringer/error interfaces are optionally invoked, including
|
||||||
|
on unexported types
|
||||||
|
* Custom types which only implement the Stringer/error interfaces via
|
||||||
|
a pointer receiver are optionally invoked when passing non-pointer
|
||||||
|
variables
|
||||||
|
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||||
|
includes offsets, byte values in hex, and ASCII output
|
||||||
|
|
||||||
|
The configuration options are controlled by an exported package global,
|
||||||
|
spew.Config. See ConfigState for options documentation.
|
||||||
|
|
||||||
|
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||||
|
get the formatted result as a string.
|
||||||
|
*/
|
||||||
|
func Dump(a ...interface{}) {
|
||||||
|
fdump(&Config, os.Stdout, a...)
|
||||||
|
}
|
||||||
1042
vendor/github.com/davecgh/go-spew/spew/dump_test.go
generated
vendored
Normal file
1042
vendor/github.com/davecgh/go-spew/spew/dump_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
98
vendor/github.com/davecgh/go-spew/spew/dumpcgo_test.go
generated
vendored
Normal file
98
vendor/github.com/davecgh/go-spew/spew/dumpcgo_test.go
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
// Copyright (c) 2013 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when both cgo is supported and "-tags testcgo" is added to the go test
|
||||||
|
// command line. This means the cgo tests are only added (and hence run) when
|
||||||
|
// specifially requested. This configuration is used because spew itself
|
||||||
|
// does not require cgo to run even though it does handle certain cgo types
|
||||||
|
// specially. Rather than forcing all clients to require cgo and an external
|
||||||
|
// C compiler just to run the tests, this scheme makes them optional.
|
||||||
|
// +build cgo,testcgo
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew/testdata"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addCgoDumpTests() {
|
||||||
|
// C char pointer.
|
||||||
|
v := testdata.GetCgoCharPointer()
|
||||||
|
nv := testdata.GetCgoNullCharPointer()
|
||||||
|
pv := &v
|
||||||
|
vcAddr := fmt.Sprintf("%p", v)
|
||||||
|
vAddr := fmt.Sprintf("%p", pv)
|
||||||
|
pvAddr := fmt.Sprintf("%p", &pv)
|
||||||
|
vt := "*testdata._Ctype_char"
|
||||||
|
vs := "116"
|
||||||
|
addDumpTest(v, "("+vt+")("+vcAddr+")("+vs+")\n")
|
||||||
|
addDumpTest(pv, "(*"+vt+")("+vAddr+"->"+vcAddr+")("+vs+")\n")
|
||||||
|
addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+"->"+vcAddr+")("+vs+")\n")
|
||||||
|
addDumpTest(nv, "("+vt+")(<nil>)\n")
|
||||||
|
|
||||||
|
// C char array.
|
||||||
|
v2, v2l, v2c := testdata.GetCgoCharArray()
|
||||||
|
v2Len := fmt.Sprintf("%d", v2l)
|
||||||
|
v2Cap := fmt.Sprintf("%d", v2c)
|
||||||
|
v2t := "[6]testdata._Ctype_char"
|
||||||
|
v2s := "(len=" + v2Len + " cap=" + v2Cap + ") " +
|
||||||
|
"{\n 00000000 74 65 73 74 32 00 " +
|
||||||
|
" |test2.|\n}"
|
||||||
|
addDumpTest(v2, "("+v2t+") "+v2s+"\n")
|
||||||
|
|
||||||
|
// C unsigned char array.
|
||||||
|
v3, v3l, v3c := testdata.GetCgoUnsignedCharArray()
|
||||||
|
v3Len := fmt.Sprintf("%d", v3l)
|
||||||
|
v3Cap := fmt.Sprintf("%d", v3c)
|
||||||
|
v3t := "[6]testdata._Ctype_unsignedchar"
|
||||||
|
v3s := "(len=" + v3Len + " cap=" + v3Cap + ") " +
|
||||||
|
"{\n 00000000 74 65 73 74 33 00 " +
|
||||||
|
" |test3.|\n}"
|
||||||
|
addDumpTest(v3, "("+v3t+") "+v3s+"\n")
|
||||||
|
|
||||||
|
// C signed char array.
|
||||||
|
v4, v4l, v4c := testdata.GetCgoSignedCharArray()
|
||||||
|
v4Len := fmt.Sprintf("%d", v4l)
|
||||||
|
v4Cap := fmt.Sprintf("%d", v4c)
|
||||||
|
v4t := "[6]testdata._Ctype_schar"
|
||||||
|
v4t2 := "testdata._Ctype_schar"
|
||||||
|
v4s := "(len=" + v4Len + " cap=" + v4Cap + ") " +
|
||||||
|
"{\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 101,\n (" + v4t2 +
|
||||||
|
") 115,\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 52,\n (" + v4t2 +
|
||||||
|
") 0\n}"
|
||||||
|
addDumpTest(v4, "("+v4t+") "+v4s+"\n")
|
||||||
|
|
||||||
|
// C uint8_t array.
|
||||||
|
v5, v5l, v5c := testdata.GetCgoUint8tArray()
|
||||||
|
v5Len := fmt.Sprintf("%d", v5l)
|
||||||
|
v5Cap := fmt.Sprintf("%d", v5c)
|
||||||
|
v5t := "[6]testdata._Ctype_uint8_t"
|
||||||
|
v5s := "(len=" + v5Len + " cap=" + v5Cap + ") " +
|
||||||
|
"{\n 00000000 74 65 73 74 35 00 " +
|
||||||
|
" |test5.|\n}"
|
||||||
|
addDumpTest(v5, "("+v5t+") "+v5s+"\n")
|
||||||
|
|
||||||
|
// C typedefed unsigned char array.
|
||||||
|
v6, v6l, v6c := testdata.GetCgoTypdefedUnsignedCharArray()
|
||||||
|
v6Len := fmt.Sprintf("%d", v6l)
|
||||||
|
v6Cap := fmt.Sprintf("%d", v6c)
|
||||||
|
v6t := "[6]testdata._Ctype_custom_uchar_t"
|
||||||
|
v6s := "(len=" + v6Len + " cap=" + v6Cap + ") " +
|
||||||
|
"{\n 00000000 74 65 73 74 36 00 " +
|
||||||
|
" |test6.|\n}"
|
||||||
|
addDumpTest(v6, "("+v6t+") "+v6s+"\n")
|
||||||
|
}
|
||||||
26
vendor/github.com/davecgh/go-spew/spew/dumpnocgo_test.go
generated
vendored
Normal file
26
vendor/github.com/davecgh/go-spew/spew/dumpnocgo_test.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) 2013 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when either cgo is not supported or "-tags testcgo" is not added to the go
|
||||||
|
// test command line. This file intentionally does not setup any cgo tests in
|
||||||
|
// this scenario.
|
||||||
|
// +build !cgo !testcgo
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
func addCgoDumpTests() {
|
||||||
|
// Don't add any tests for cgo since this file is only compiled when
|
||||||
|
// there should not be any cgo tests.
|
||||||
|
}
|
||||||
226
vendor/github.com/davecgh/go-spew/spew/example_test.go
generated
vendored
Normal file
226
vendor/github.com/davecgh/go-spew/spew/example_test.go
generated
vendored
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Flag int
|
||||||
|
|
||||||
|
const (
|
||||||
|
flagOne Flag = iota
|
||||||
|
flagTwo
|
||||||
|
)
|
||||||
|
|
||||||
|
var flagStrings = map[Flag]string{
|
||||||
|
flagOne: "flagOne",
|
||||||
|
flagTwo: "flagTwo",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Flag) String() string {
|
||||||
|
if s, ok := flagStrings[f]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Unknown flag (%d)", int(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bar struct {
|
||||||
|
data uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type Foo struct {
|
||||||
|
unexportedField Bar
|
||||||
|
ExportedField map[interface{}]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use Dump to dump variables to stdout.
|
||||||
|
func ExampleDump() {
|
||||||
|
// The following package level declarations are assumed for this example:
|
||||||
|
/*
|
||||||
|
type Flag int
|
||||||
|
|
||||||
|
const (
|
||||||
|
flagOne Flag = iota
|
||||||
|
flagTwo
|
||||||
|
)
|
||||||
|
|
||||||
|
var flagStrings = map[Flag]string{
|
||||||
|
flagOne: "flagOne",
|
||||||
|
flagTwo: "flagTwo",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Flag) String() string {
|
||||||
|
if s, ok := flagStrings[f]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Unknown flag (%d)", int(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bar struct {
|
||||||
|
data uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type Foo struct {
|
||||||
|
unexportedField Bar
|
||||||
|
ExportedField map[interface{}]interface{}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Setup some sample data structures for the example.
|
||||||
|
bar := Bar{uintptr(0)}
|
||||||
|
s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
|
||||||
|
f := Flag(5)
|
||||||
|
b := []byte{
|
||||||
|
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
||||||
|
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
|
||||||
|
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
|
||||||
|
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
|
||||||
|
0x31, 0x32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump!
|
||||||
|
spew.Dump(s1, f, b)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// (spew_test.Foo) {
|
||||||
|
// unexportedField: (spew_test.Bar) {
|
||||||
|
// data: (uintptr) <nil>
|
||||||
|
// },
|
||||||
|
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||||
|
// (string) (len=3) "one": (bool) true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// (spew_test.Flag) Unknown flag (5)
|
||||||
|
// ([]uint8) (len=34 cap=34) {
|
||||||
|
// 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||||
|
// 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||||
|
// 00000020 31 32 |12|
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use Printf to display a variable with a
|
||||||
|
// format string and inline formatting.
|
||||||
|
func ExamplePrintf() {
|
||||||
|
// Create a double pointer to a uint 8.
|
||||||
|
ui8 := uint8(5)
|
||||||
|
pui8 := &ui8
|
||||||
|
ppui8 := &pui8
|
||||||
|
|
||||||
|
// Create a circular data type.
|
||||||
|
type circular struct {
|
||||||
|
ui8 uint8
|
||||||
|
c *circular
|
||||||
|
}
|
||||||
|
c := circular{ui8: 1}
|
||||||
|
c.c = &c
|
||||||
|
|
||||||
|
// Print!
|
||||||
|
spew.Printf("ppui8: %v\n", ppui8)
|
||||||
|
spew.Printf("circular: %v\n", c)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// ppui8: <**>5
|
||||||
|
// circular: {1 <*>{1 <*><shown>}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use a ConfigState.
|
||||||
|
func ExampleConfigState() {
|
||||||
|
// Modify the indent level of the ConfigState only. The global
|
||||||
|
// configuration is not modified.
|
||||||
|
scs := spew.ConfigState{Indent: "\t"}
|
||||||
|
|
||||||
|
// Output using the ConfigState instance.
|
||||||
|
v := map[string]int{"one": 1}
|
||||||
|
scs.Printf("v: %v\n", v)
|
||||||
|
scs.Dump(v)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// v: map[one:1]
|
||||||
|
// (map[string]int) (len=1) {
|
||||||
|
// (string) (len=3) "one": (int) 1
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use ConfigState.Dump to dump variables to
|
||||||
|
// stdout
|
||||||
|
func ExampleConfigState_Dump() {
|
||||||
|
// See the top-level Dump example for details on the types used in this
|
||||||
|
// example.
|
||||||
|
|
||||||
|
// Create two ConfigState instances with different indentation.
|
||||||
|
scs := spew.ConfigState{Indent: "\t"}
|
||||||
|
scs2 := spew.ConfigState{Indent: " "}
|
||||||
|
|
||||||
|
// Setup some sample data structures for the example.
|
||||||
|
bar := Bar{uintptr(0)}
|
||||||
|
s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
|
||||||
|
|
||||||
|
// Dump using the ConfigState instances.
|
||||||
|
scs.Dump(s1)
|
||||||
|
scs2.Dump(s1)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// (spew_test.Foo) {
|
||||||
|
// unexportedField: (spew_test.Bar) {
|
||||||
|
// data: (uintptr) <nil>
|
||||||
|
// },
|
||||||
|
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||||
|
// (string) (len=3) "one": (bool) true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// (spew_test.Foo) {
|
||||||
|
// unexportedField: (spew_test.Bar) {
|
||||||
|
// data: (uintptr) <nil>
|
||||||
|
// },
|
||||||
|
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||||
|
// (string) (len=3) "one": (bool) true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use ConfigState.Printf to display a variable
|
||||||
|
// with a format string and inline formatting.
|
||||||
|
func ExampleConfigState_Printf() {
|
||||||
|
// See the top-level Dump example for details on the types used in this
|
||||||
|
// example.
|
||||||
|
|
||||||
|
// Create two ConfigState instances and modify the method handling of the
|
||||||
|
// first ConfigState only.
|
||||||
|
scs := spew.NewDefaultConfig()
|
||||||
|
scs2 := spew.NewDefaultConfig()
|
||||||
|
scs.DisableMethods = true
|
||||||
|
|
||||||
|
// Alternatively
|
||||||
|
// scs := spew.ConfigState{Indent: " ", DisableMethods: true}
|
||||||
|
// scs2 := spew.ConfigState{Indent: " "}
|
||||||
|
|
||||||
|
// This is of type Flag which implements a Stringer and has raw value 1.
|
||||||
|
f := flagTwo
|
||||||
|
|
||||||
|
// Dump using the ConfigState instances.
|
||||||
|
scs.Printf("f: %v\n", f)
|
||||||
|
scs2.Printf("f: %v\n", f)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// f: 1
|
||||||
|
// f: flagTwo
|
||||||
|
}
|
||||||
419
vendor/github.com/davecgh/go-spew/spew/format.go
generated
vendored
Normal file
419
vendor/github.com/davecgh/go-spew/spew/format.go
generated
vendored
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// supportedFlags is a list of all the character flags supported by fmt package.
|
||||||
|
const supportedFlags = "0-+# "
|
||||||
|
|
||||||
|
// formatState implements the fmt.Formatter interface and contains information
|
||||||
|
// about the state of a formatting operation. The NewFormatter function can
|
||||||
|
// be used to get a new Formatter which can be used directly as arguments
|
||||||
|
// in standard fmt package printing calls.
|
||||||
|
type formatState struct {
|
||||||
|
value interface{}
|
||||||
|
fs fmt.State
|
||||||
|
depth int
|
||||||
|
pointers map[uintptr]int
|
||||||
|
ignoreNextType bool
|
||||||
|
cs *ConfigState
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildDefaultFormat recreates the original format string without precision
|
||||||
|
// and width information to pass in to fmt.Sprintf in the case of an
|
||||||
|
// unrecognized type. Unless new types are added to the language, this
|
||||||
|
// function won't ever be called.
|
||||||
|
func (f *formatState) buildDefaultFormat() (format string) {
|
||||||
|
buf := bytes.NewBuffer(percentBytes)
|
||||||
|
|
||||||
|
for _, flag := range supportedFlags {
|
||||||
|
if f.fs.Flag(int(flag)) {
|
||||||
|
buf.WriteRune(flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteRune('v')
|
||||||
|
|
||||||
|
format = buf.String()
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
|
// constructOrigFormat recreates the original format string including precision
|
||||||
|
// and width information to pass along to the standard fmt package. This allows
|
||||||
|
// automatic deferral of all format strings this package doesn't support.
|
||||||
|
func (f *formatState) constructOrigFormat(verb rune) (format string) {
|
||||||
|
buf := bytes.NewBuffer(percentBytes)
|
||||||
|
|
||||||
|
for _, flag := range supportedFlags {
|
||||||
|
if f.fs.Flag(int(flag)) {
|
||||||
|
buf.WriteRune(flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if width, ok := f.fs.Width(); ok {
|
||||||
|
buf.WriteString(strconv.Itoa(width))
|
||||||
|
}
|
||||||
|
|
||||||
|
if precision, ok := f.fs.Precision(); ok {
|
||||||
|
buf.Write(precisionBytes)
|
||||||
|
buf.WriteString(strconv.Itoa(precision))
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteRune(verb)
|
||||||
|
|
||||||
|
format = buf.String()
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpackValue returns values inside of non-nil interfaces when possible and
|
||||||
|
// ensures that types for values which have been unpacked from an interface
|
||||||
|
// are displayed when the show types flag is also set.
|
||||||
|
// This is useful for data types like structs, arrays, slices, and maps which
|
||||||
|
// can contain varying types packed inside an interface.
|
||||||
|
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
|
||||||
|
if v.Kind() == reflect.Interface {
|
||||||
|
f.ignoreNextType = false
|
||||||
|
if !v.IsNil() {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatPtr handles formatting of pointers by indirecting them as necessary.
|
||||||
|
func (f *formatState) formatPtr(v reflect.Value) {
|
||||||
|
// Display nil if top level pointer is nil.
|
||||||
|
showTypes := f.fs.Flag('#')
|
||||||
|
if v.IsNil() && (!showTypes || f.ignoreNextType) {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove pointers at or below the current depth from map used to detect
|
||||||
|
// circular refs.
|
||||||
|
for k, depth := range f.pointers {
|
||||||
|
if depth >= f.depth {
|
||||||
|
delete(f.pointers, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep list of all dereferenced pointers to possibly show later.
|
||||||
|
pointerChain := make([]uintptr, 0)
|
||||||
|
|
||||||
|
// Figure out how many levels of indirection there are by derferencing
|
||||||
|
// pointers and unpacking interfaces down the chain while detecting circular
|
||||||
|
// references.
|
||||||
|
nilFound := false
|
||||||
|
cycleFound := false
|
||||||
|
indirects := 0
|
||||||
|
ve := v
|
||||||
|
for ve.Kind() == reflect.Ptr {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
indirects++
|
||||||
|
addr := ve.Pointer()
|
||||||
|
pointerChain = append(pointerChain, addr)
|
||||||
|
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
|
||||||
|
cycleFound = true
|
||||||
|
indirects--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
f.pointers[addr] = f.depth
|
||||||
|
|
||||||
|
ve = ve.Elem()
|
||||||
|
if ve.Kind() == reflect.Interface {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ve = ve.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display type or indirection level depending on flags.
|
||||||
|
if showTypes && !f.ignoreNextType {
|
||||||
|
f.fs.Write(openParenBytes)
|
||||||
|
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||||
|
f.fs.Write([]byte(ve.Type().String()))
|
||||||
|
f.fs.Write(closeParenBytes)
|
||||||
|
} else {
|
||||||
|
if nilFound || cycleFound {
|
||||||
|
indirects += strings.Count(ve.Type().String(), "*")
|
||||||
|
}
|
||||||
|
f.fs.Write(openAngleBytes)
|
||||||
|
f.fs.Write([]byte(strings.Repeat("*", indirects)))
|
||||||
|
f.fs.Write(closeAngleBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display pointer information depending on flags.
|
||||||
|
if f.fs.Flag('+') && (len(pointerChain) > 0) {
|
||||||
|
f.fs.Write(openParenBytes)
|
||||||
|
for i, addr := range pointerChain {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(pointerChainBytes)
|
||||||
|
}
|
||||||
|
printHexPtr(f.fs, addr)
|
||||||
|
}
|
||||||
|
f.fs.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display dereferenced value.
|
||||||
|
switch {
|
||||||
|
case nilFound == true:
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
|
||||||
|
case cycleFound == true:
|
||||||
|
f.fs.Write(circularShortBytes)
|
||||||
|
|
||||||
|
default:
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(ve)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// format is the main workhorse for providing the Formatter interface. It
|
||||||
|
// uses the passed reflect value to figure out what kind of object we are
|
||||||
|
// dealing with and formats it appropriately. It is a recursive function,
|
||||||
|
// however circular data structures are detected and handled properly.
|
||||||
|
func (f *formatState) format(v reflect.Value) {
|
||||||
|
// Handle invalid reflect values immediately.
|
||||||
|
kind := v.Kind()
|
||||||
|
if kind == reflect.Invalid {
|
||||||
|
f.fs.Write(invalidAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle pointers specially.
|
||||||
|
if kind == reflect.Ptr {
|
||||||
|
f.formatPtr(v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print type information unless already handled elsewhere.
|
||||||
|
if !f.ignoreNextType && f.fs.Flag('#') {
|
||||||
|
f.fs.Write(openParenBytes)
|
||||||
|
f.fs.Write([]byte(v.Type().String()))
|
||||||
|
f.fs.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
f.ignoreNextType = false
|
||||||
|
|
||||||
|
// Call Stringer/error interfaces if they exist and the handle methods
|
||||||
|
// flag is enabled.
|
||||||
|
if !f.cs.DisableMethods {
|
||||||
|
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||||
|
if handled := handleMethods(f.cs, f.fs, v); handled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.Invalid:
|
||||||
|
// Do nothing. We should never get here since invalid has already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
printBool(f.fs, v.Bool())
|
||||||
|
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
printInt(f.fs, v.Int(), 10)
|
||||||
|
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
printUint(f.fs, v.Uint(), 10)
|
||||||
|
|
||||||
|
case reflect.Float32:
|
||||||
|
printFloat(f.fs, v.Float(), 32)
|
||||||
|
|
||||||
|
case reflect.Float64:
|
||||||
|
printFloat(f.fs, v.Float(), 64)
|
||||||
|
|
||||||
|
case reflect.Complex64:
|
||||||
|
printComplex(f.fs, v.Complex(), 32)
|
||||||
|
|
||||||
|
case reflect.Complex128:
|
||||||
|
printComplex(f.fs, v.Complex(), 64)
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.IsNil() {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case reflect.Array:
|
||||||
|
f.fs.Write(openBracketBytes)
|
||||||
|
f.depth++
|
||||||
|
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||||
|
f.fs.Write(maxShortBytes)
|
||||||
|
} else {
|
||||||
|
numEntries := v.Len()
|
||||||
|
for i := 0; i < numEntries; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(f.unpackValue(v.Index(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.depth--
|
||||||
|
f.fs.Write(closeBracketBytes)
|
||||||
|
|
||||||
|
case reflect.String:
|
||||||
|
f.fs.Write([]byte(v.String()))
|
||||||
|
|
||||||
|
case reflect.Interface:
|
||||||
|
// The only time we should get here is for nil interfaces due to
|
||||||
|
// unpackValue calls.
|
||||||
|
if v.IsNil() {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
// Do nothing. We should never get here since pointers have already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
// nil maps should be indicated as different than empty maps
|
||||||
|
if v.IsNil() {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
f.fs.Write(openMapBytes)
|
||||||
|
f.depth++
|
||||||
|
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||||
|
f.fs.Write(maxShortBytes)
|
||||||
|
} else {
|
||||||
|
keys := v.MapKeys()
|
||||||
|
if f.cs.SortKeys {
|
||||||
|
sortValues(keys, f.cs)
|
||||||
|
}
|
||||||
|
for i, key := range keys {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(f.unpackValue(key))
|
||||||
|
f.fs.Write(colonBytes)
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(f.unpackValue(v.MapIndex(key)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.depth--
|
||||||
|
f.fs.Write(closeMapBytes)
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
numFields := v.NumField()
|
||||||
|
f.fs.Write(openBraceBytes)
|
||||||
|
f.depth++
|
||||||
|
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||||
|
f.fs.Write(maxShortBytes)
|
||||||
|
} else {
|
||||||
|
vt := v.Type()
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
vtf := vt.Field(i)
|
||||||
|
if f.fs.Flag('+') || f.fs.Flag('#') {
|
||||||
|
f.fs.Write([]byte(vtf.Name))
|
||||||
|
f.fs.Write(colonBytes)
|
||||||
|
}
|
||||||
|
f.format(f.unpackValue(v.Field(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.depth--
|
||||||
|
f.fs.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.Uintptr:
|
||||||
|
printHexPtr(f.fs, uintptr(v.Uint()))
|
||||||
|
|
||||||
|
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||||
|
printHexPtr(f.fs, v.Pointer())
|
||||||
|
|
||||||
|
// There were not any other types at the time this code was written, but
|
||||||
|
// fall back to letting the default fmt package handle it if any get added.
|
||||||
|
default:
|
||||||
|
format := f.buildDefaultFormat()
|
||||||
|
if v.CanInterface() {
|
||||||
|
fmt.Fprintf(f.fs, format, v.Interface())
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(f.fs, format, v.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
|
||||||
|
// details.
|
||||||
|
func (f *formatState) Format(fs fmt.State, verb rune) {
|
||||||
|
f.fs = fs
|
||||||
|
|
||||||
|
// Use standard formatting for verbs that are not v.
|
||||||
|
if verb != 'v' {
|
||||||
|
format := f.constructOrigFormat(verb)
|
||||||
|
fmt.Fprintf(fs, format, f.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.value == nil {
|
||||||
|
if fs.Flag('#') {
|
||||||
|
fs.Write(interfaceBytes)
|
||||||
|
}
|
||||||
|
fs.Write(nilAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f.format(reflect.ValueOf(f.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFormatter is a helper function to consolidate the logic from the various
|
||||||
|
// public methods which take varying config states.
|
||||||
|
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
|
||||||
|
fs := &formatState{value: v, cs: cs}
|
||||||
|
fs.pointers = make(map[uintptr]int)
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||||
|
interface. As a result, it integrates cleanly with standard fmt package
|
||||||
|
printing functions. The formatter is useful for inline printing of smaller data
|
||||||
|
types similar to the standard %v format specifier.
|
||||||
|
|
||||||
|
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||||
|
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||||
|
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||||
|
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||||
|
the width and precision arguments (however they will still work on the format
|
||||||
|
specifiers not handled by the custom formatter).
|
||||||
|
|
||||||
|
Typically this function shouldn't be called directly. It is much easier to make
|
||||||
|
use of the custom formatter by calling one of the convenience functions such as
|
||||||
|
Printf, Println, or Fprintf.
|
||||||
|
*/
|
||||||
|
func NewFormatter(v interface{}) fmt.Formatter {
|
||||||
|
return newFormatter(&Config, v)
|
||||||
|
}
|
||||||
1558
vendor/github.com/davecgh/go-spew/spew/format_test.go
generated
vendored
Normal file
1558
vendor/github.com/davecgh/go-spew/spew/format_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
87
vendor/github.com/davecgh/go-spew/spew/internal_test.go
generated
vendored
Normal file
87
vendor/github.com/davecgh/go-spew/spew/internal_test.go
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test file is part of the spew package rather than than the spew_test
|
||||||
|
package because it needs access to internals to properly test certain cases
|
||||||
|
which are not possible via the public interface since they should never happen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// dummyFmtState implements a fake fmt.State to use for testing invalid
|
||||||
|
// reflect.Value handling. This is necessary because the fmt package catches
|
||||||
|
// invalid values before invoking the formatter on them.
|
||||||
|
type dummyFmtState struct {
|
||||||
|
bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dfs *dummyFmtState) Flag(f int) bool {
|
||||||
|
if f == int('+') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dfs *dummyFmtState) Precision() (int, bool) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dfs *dummyFmtState) Width() (int, bool) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestInvalidReflectValue ensures the dump and formatter code handles an
|
||||||
|
// invalid reflect value properly. This needs access to internal state since it
|
||||||
|
// should never happen in real code and therefore can't be tested via the public
|
||||||
|
// API.
|
||||||
|
func TestInvalidReflectValue(t *testing.T) {
|
||||||
|
i := 1
|
||||||
|
|
||||||
|
// Dump invalid reflect value.
|
||||||
|
v := new(reflect.Value)
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
d := dumpState{w: buf, cs: &Config}
|
||||||
|
d.dump(*v)
|
||||||
|
s := buf.String()
|
||||||
|
want := "<invalid>"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("InvalidReflectValue #%d\n got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
// Formatter invalid reflect value.
|
||||||
|
buf2 := new(dummyFmtState)
|
||||||
|
f := formatState{value: *v, cs: &Config, fs: buf2}
|
||||||
|
f.format(*v)
|
||||||
|
s = buf2.String()
|
||||||
|
want = "<invalid>"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("InvalidReflectValue #%d got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortValues makes the internal sortValues function available to the test
|
||||||
|
// package.
|
||||||
|
func SortValues(values []reflect.Value, cs *ConfigState) {
|
||||||
|
sortValues(values, cs)
|
||||||
|
}
|
||||||
101
vendor/github.com/davecgh/go-spew/spew/internalunsafe_test.go
generated
vendored
Normal file
101
vendor/github.com/davecgh/go-spew/spew/internalunsafe_test.go
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
// Copyright (c) 2013-2015 Dave Collins <dave@davec.name>
|
||||||
|
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when the code is not running on Google App Engine and "-tags disableunsafe"
|
||||||
|
// is not added to the go build command line.
|
||||||
|
// +build !appengine,!disableunsafe
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test file is part of the spew package rather than than the spew_test
|
||||||
|
package because it needs access to internals to properly test certain cases
|
||||||
|
which are not possible via the public interface since they should never happen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// changeKind uses unsafe to intentionally change the kind of a reflect.Value to
|
||||||
|
// the maximum kind value which does not exist. This is needed to test the
|
||||||
|
// fallback code which punts to the standard fmt library for new types that
|
||||||
|
// might get added to the language.
|
||||||
|
func changeKind(v *reflect.Value, readOnly bool) {
|
||||||
|
rvf := (*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + offsetFlag))
|
||||||
|
*rvf = *rvf | ((1<<flagKindWidth - 1) << flagKindShift)
|
||||||
|
if readOnly {
|
||||||
|
*rvf |= flagRO
|
||||||
|
} else {
|
||||||
|
*rvf &= ^uintptr(flagRO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAddedReflectValue tests functionaly of the dump and formatter code which
|
||||||
|
// falls back to the standard fmt library for new types that might get added to
|
||||||
|
// the language.
|
||||||
|
func TestAddedReflectValue(t *testing.T) {
|
||||||
|
i := 1
|
||||||
|
|
||||||
|
// Dump using a reflect.Value that is exported.
|
||||||
|
v := reflect.ValueOf(int8(5))
|
||||||
|
changeKind(&v, false)
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
d := dumpState{w: buf, cs: &Config}
|
||||||
|
d.dump(v)
|
||||||
|
s := buf.String()
|
||||||
|
want := "(int8) 5"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
// Dump using a reflect.Value that is not exported.
|
||||||
|
changeKind(&v, true)
|
||||||
|
buf.Reset()
|
||||||
|
d.dump(v)
|
||||||
|
s = buf.String()
|
||||||
|
want = "(int8) <int8 Value>"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
// Formatter using a reflect.Value that is exported.
|
||||||
|
changeKind(&v, false)
|
||||||
|
buf2 := new(dummyFmtState)
|
||||||
|
f := formatState{value: v, cs: &Config, fs: buf2}
|
||||||
|
f.format(v)
|
||||||
|
s = buf2.String()
|
||||||
|
want = "5"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
// Formatter using a reflect.Value that is not exported.
|
||||||
|
changeKind(&v, true)
|
||||||
|
buf2.Reset()
|
||||||
|
f = formatState{value: v, cs: &Config, fs: buf2}
|
||||||
|
f.format(v)
|
||||||
|
s = buf2.String()
|
||||||
|
want = "<int8 Value>"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
148
vendor/github.com/davecgh/go-spew/spew/spew.go
generated
vendored
Normal file
148
vendor/github.com/davecgh/go-spew/spew/spew.go
generated
vendored
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the formatted string as a value that satisfies error. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Errorf(format string, a ...interface{}) (err error) {
|
||||||
|
return fmt.Errorf(format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprint(w, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintf(w, format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintln(w, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Print(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Print(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Printf(format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Printf(format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Println(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Println(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Sprint(a ...interface{}) string {
|
||||||
|
return fmt.Sprint(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Sprintf(format string, a ...interface{}) string {
|
||||||
|
return fmt.Sprintf(format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||||
|
// were passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Sprintln(a ...interface{}) string {
|
||||||
|
return fmt.Sprintln(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||||
|
// length with each argument converted to a default spew Formatter interface.
|
||||||
|
func convertArgs(args []interface{}) (formatters []interface{}) {
|
||||||
|
formatters = make([]interface{}, len(args))
|
||||||
|
for index, arg := range args {
|
||||||
|
formatters[index] = NewFormatter(arg)
|
||||||
|
}
|
||||||
|
return formatters
|
||||||
|
}
|
||||||
309
vendor/github.com/davecgh/go-spew/spew/spew_test.go
generated
vendored
Normal file
309
vendor/github.com/davecgh/go-spew/spew/spew_test.go
generated
vendored
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// spewFunc is used to identify which public function of the spew package or
|
||||||
|
// ConfigState a test applies to.
|
||||||
|
type spewFunc int
|
||||||
|
|
||||||
|
const (
|
||||||
|
fCSFdump spewFunc = iota
|
||||||
|
fCSFprint
|
||||||
|
fCSFprintf
|
||||||
|
fCSFprintln
|
||||||
|
fCSPrint
|
||||||
|
fCSPrintln
|
||||||
|
fCSSdump
|
||||||
|
fCSSprint
|
||||||
|
fCSSprintf
|
||||||
|
fCSSprintln
|
||||||
|
fCSErrorf
|
||||||
|
fCSNewFormatter
|
||||||
|
fErrorf
|
||||||
|
fFprint
|
||||||
|
fFprintln
|
||||||
|
fPrint
|
||||||
|
fPrintln
|
||||||
|
fSdump
|
||||||
|
fSprint
|
||||||
|
fSprintf
|
||||||
|
fSprintln
|
||||||
|
)
|
||||||
|
|
||||||
|
// Map of spewFunc values to names for pretty printing.
|
||||||
|
var spewFuncStrings = map[spewFunc]string{
|
||||||
|
fCSFdump: "ConfigState.Fdump",
|
||||||
|
fCSFprint: "ConfigState.Fprint",
|
||||||
|
fCSFprintf: "ConfigState.Fprintf",
|
||||||
|
fCSFprintln: "ConfigState.Fprintln",
|
||||||
|
fCSSdump: "ConfigState.Sdump",
|
||||||
|
fCSPrint: "ConfigState.Print",
|
||||||
|
fCSPrintln: "ConfigState.Println",
|
||||||
|
fCSSprint: "ConfigState.Sprint",
|
||||||
|
fCSSprintf: "ConfigState.Sprintf",
|
||||||
|
fCSSprintln: "ConfigState.Sprintln",
|
||||||
|
fCSErrorf: "ConfigState.Errorf",
|
||||||
|
fCSNewFormatter: "ConfigState.NewFormatter",
|
||||||
|
fErrorf: "spew.Errorf",
|
||||||
|
fFprint: "spew.Fprint",
|
||||||
|
fFprintln: "spew.Fprintln",
|
||||||
|
fPrint: "spew.Print",
|
||||||
|
fPrintln: "spew.Println",
|
||||||
|
fSdump: "spew.Sdump",
|
||||||
|
fSprint: "spew.Sprint",
|
||||||
|
fSprintf: "spew.Sprintf",
|
||||||
|
fSprintln: "spew.Sprintln",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f spewFunc) String() string {
|
||||||
|
if s, ok := spewFuncStrings[f]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Unknown spewFunc (%d)", int(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// spewTest is used to describe a test to be performed against the public
|
||||||
|
// functions of the spew package or ConfigState.
|
||||||
|
type spewTest struct {
|
||||||
|
cs *spew.ConfigState
|
||||||
|
f spewFunc
|
||||||
|
format string
|
||||||
|
in interface{}
|
||||||
|
want string
|
||||||
|
}
|
||||||
|
|
||||||
|
// spewTests houses the tests to be performed against the public functions of
|
||||||
|
// the spew package and ConfigState.
|
||||||
|
//
|
||||||
|
// These tests are only intended to ensure the public functions are exercised
|
||||||
|
// and are intentionally not exhaustive of types. The exhaustive type
|
||||||
|
// tests are handled in the dump and format tests.
|
||||||
|
var spewTests []spewTest
|
||||||
|
|
||||||
|
// redirStdout is a helper function to return the standard output from f as a
|
||||||
|
// byte slice.
|
||||||
|
func redirStdout(f func()) ([]byte, error) {
|
||||||
|
tempFile, err := ioutil.TempFile("", "ss-test")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fileName := tempFile.Name()
|
||||||
|
defer os.Remove(fileName) // Ignore error
|
||||||
|
|
||||||
|
origStdout := os.Stdout
|
||||||
|
os.Stdout = tempFile
|
||||||
|
f()
|
||||||
|
os.Stdout = origStdout
|
||||||
|
tempFile.Close()
|
||||||
|
|
||||||
|
return ioutil.ReadFile(fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initSpewTests() {
|
||||||
|
// Config states with various settings.
|
||||||
|
scsDefault := spew.NewDefaultConfig()
|
||||||
|
scsNoMethods := &spew.ConfigState{Indent: " ", DisableMethods: true}
|
||||||
|
scsNoPmethods := &spew.ConfigState{Indent: " ", DisablePointerMethods: true}
|
||||||
|
scsMaxDepth := &spew.ConfigState{Indent: " ", MaxDepth: 1}
|
||||||
|
scsContinue := &spew.ConfigState{Indent: " ", ContinueOnMethod: true}
|
||||||
|
|
||||||
|
// Variables for tests on types which implement Stringer interface with and
|
||||||
|
// without a pointer receiver.
|
||||||
|
ts := stringer("test")
|
||||||
|
tps := pstringer("test")
|
||||||
|
|
||||||
|
// depthTester is used to test max depth handling for structs, array, slices
|
||||||
|
// and maps.
|
||||||
|
type depthTester struct {
|
||||||
|
ic indirCir1
|
||||||
|
arr [1]string
|
||||||
|
slice []string
|
||||||
|
m map[string]int
|
||||||
|
}
|
||||||
|
dt := depthTester{indirCir1{nil}, [1]string{"arr"}, []string{"slice"},
|
||||||
|
map[string]int{"one": 1}}
|
||||||
|
|
||||||
|
// Variable for tests on types which implement error interface.
|
||||||
|
te := customError(10)
|
||||||
|
|
||||||
|
spewTests = []spewTest{
|
||||||
|
{scsDefault, fCSFdump, "", int8(127), "(int8) 127\n"},
|
||||||
|
{scsDefault, fCSFprint, "", int16(32767), "32767"},
|
||||||
|
{scsDefault, fCSFprintf, "%v", int32(2147483647), "2147483647"},
|
||||||
|
{scsDefault, fCSFprintln, "", int(2147483647), "2147483647\n"},
|
||||||
|
{scsDefault, fCSPrint, "", int64(9223372036854775807), "9223372036854775807"},
|
||||||
|
{scsDefault, fCSPrintln, "", uint8(255), "255\n"},
|
||||||
|
{scsDefault, fCSSdump, "", uint8(64), "(uint8) 64\n"},
|
||||||
|
{scsDefault, fCSSprint, "", complex(1, 2), "(1+2i)"},
|
||||||
|
{scsDefault, fCSSprintf, "%v", complex(float32(3), 4), "(3+4i)"},
|
||||||
|
{scsDefault, fCSSprintln, "", complex(float64(5), 6), "(5+6i)\n"},
|
||||||
|
{scsDefault, fCSErrorf, "%#v", uint16(65535), "(uint16)65535"},
|
||||||
|
{scsDefault, fCSNewFormatter, "%v", uint32(4294967295), "4294967295"},
|
||||||
|
{scsDefault, fErrorf, "%v", uint64(18446744073709551615), "18446744073709551615"},
|
||||||
|
{scsDefault, fFprint, "", float32(3.14), "3.14"},
|
||||||
|
{scsDefault, fFprintln, "", float64(6.28), "6.28\n"},
|
||||||
|
{scsDefault, fPrint, "", true, "true"},
|
||||||
|
{scsDefault, fPrintln, "", false, "false\n"},
|
||||||
|
{scsDefault, fSdump, "", complex(-10, -20), "(complex128) (-10-20i)\n"},
|
||||||
|
{scsDefault, fSprint, "", complex(-1, -2), "(-1-2i)"},
|
||||||
|
{scsDefault, fSprintf, "%v", complex(float32(-3), -4), "(-3-4i)"},
|
||||||
|
{scsDefault, fSprintln, "", complex(float64(-5), -6), "(-5-6i)\n"},
|
||||||
|
{scsNoMethods, fCSFprint, "", ts, "test"},
|
||||||
|
{scsNoMethods, fCSFprint, "", &ts, "<*>test"},
|
||||||
|
{scsNoMethods, fCSFprint, "", tps, "test"},
|
||||||
|
{scsNoMethods, fCSFprint, "", &tps, "<*>test"},
|
||||||
|
{scsNoPmethods, fCSFprint, "", ts, "stringer test"},
|
||||||
|
{scsNoPmethods, fCSFprint, "", &ts, "<*>stringer test"},
|
||||||
|
{scsNoPmethods, fCSFprint, "", tps, "test"},
|
||||||
|
{scsNoPmethods, fCSFprint, "", &tps, "<*>stringer test"},
|
||||||
|
{scsMaxDepth, fCSFprint, "", dt, "{{<max>} [<max>] [<max>] map[<max>]}"},
|
||||||
|
{scsMaxDepth, fCSFdump, "", dt, "(spew_test.depthTester) {\n" +
|
||||||
|
" ic: (spew_test.indirCir1) {\n <max depth reached>\n },\n" +
|
||||||
|
" arr: ([1]string) (len=1 cap=1) {\n <max depth reached>\n },\n" +
|
||||||
|
" slice: ([]string) (len=1 cap=1) {\n <max depth reached>\n },\n" +
|
||||||
|
" m: (map[string]int) (len=1) {\n <max depth reached>\n }\n}\n"},
|
||||||
|
{scsContinue, fCSFprint, "", ts, "(stringer test) test"},
|
||||||
|
{scsContinue, fCSFdump, "", ts, "(spew_test.stringer) " +
|
||||||
|
"(len=4) (stringer test) \"test\"\n"},
|
||||||
|
{scsContinue, fCSFprint, "", te, "(error: 10) 10"},
|
||||||
|
{scsContinue, fCSFdump, "", te, "(spew_test.customError) " +
|
||||||
|
"(error: 10) 10\n"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSpew executes all of the tests described by spewTests.
|
||||||
|
func TestSpew(t *testing.T) {
|
||||||
|
initSpewTests()
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(spewTests))
|
||||||
|
for i, test := range spewTests {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
switch test.f {
|
||||||
|
case fCSFdump:
|
||||||
|
test.cs.Fdump(buf, test.in)
|
||||||
|
|
||||||
|
case fCSFprint:
|
||||||
|
test.cs.Fprint(buf, test.in)
|
||||||
|
|
||||||
|
case fCSFprintf:
|
||||||
|
test.cs.Fprintf(buf, test.format, test.in)
|
||||||
|
|
||||||
|
case fCSFprintln:
|
||||||
|
test.cs.Fprintln(buf, test.in)
|
||||||
|
|
||||||
|
case fCSPrint:
|
||||||
|
b, err := redirStdout(func() { test.cs.Print(test.in) })
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v #%d %v", test.f, i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.Write(b)
|
||||||
|
|
||||||
|
case fCSPrintln:
|
||||||
|
b, err := redirStdout(func() { test.cs.Println(test.in) })
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v #%d %v", test.f, i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.Write(b)
|
||||||
|
|
||||||
|
case fCSSdump:
|
||||||
|
str := test.cs.Sdump(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fCSSprint:
|
||||||
|
str := test.cs.Sprint(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fCSSprintf:
|
||||||
|
str := test.cs.Sprintf(test.format, test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fCSSprintln:
|
||||||
|
str := test.cs.Sprintln(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fCSErrorf:
|
||||||
|
err := test.cs.Errorf(test.format, test.in)
|
||||||
|
buf.WriteString(err.Error())
|
||||||
|
|
||||||
|
case fCSNewFormatter:
|
||||||
|
fmt.Fprintf(buf, test.format, test.cs.NewFormatter(test.in))
|
||||||
|
|
||||||
|
case fErrorf:
|
||||||
|
err := spew.Errorf(test.format, test.in)
|
||||||
|
buf.WriteString(err.Error())
|
||||||
|
|
||||||
|
case fFprint:
|
||||||
|
spew.Fprint(buf, test.in)
|
||||||
|
|
||||||
|
case fFprintln:
|
||||||
|
spew.Fprintln(buf, test.in)
|
||||||
|
|
||||||
|
case fPrint:
|
||||||
|
b, err := redirStdout(func() { spew.Print(test.in) })
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v #%d %v", test.f, i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.Write(b)
|
||||||
|
|
||||||
|
case fPrintln:
|
||||||
|
b, err := redirStdout(func() { spew.Println(test.in) })
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v #%d %v", test.f, i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.Write(b)
|
||||||
|
|
||||||
|
case fSdump:
|
||||||
|
str := spew.Sdump(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fSprint:
|
||||||
|
str := spew.Sprint(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fSprintf:
|
||||||
|
str := spew.Sprintf(test.format, test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fSprintln:
|
||||||
|
str := spew.Sprintln(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
default:
|
||||||
|
t.Errorf("%v #%d unrecognized function", test.f, i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s := buf.String()
|
||||||
|
if test.want != s {
|
||||||
|
t.Errorf("ConfigState #%d\n got: %s want: %s", i, s, test.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
82
vendor/github.com/davecgh/go-spew/spew/testdata/dumpcgo.go
generated
vendored
Normal file
82
vendor/github.com/davecgh/go-spew/spew/testdata/dumpcgo.go
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// Copyright (c) 2013 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when both cgo is supported and "-tags testcgo" is added to the go test
|
||||||
|
// command line. This code should really only be in the dumpcgo_test.go file,
|
||||||
|
// but unfortunately Go will not allow cgo in test files, so this is a
|
||||||
|
// workaround to allow cgo types to be tested. This configuration is used
|
||||||
|
// because spew itself does not require cgo to run even though it does handle
|
||||||
|
// certain cgo types specially. Rather than forcing all clients to require cgo
|
||||||
|
// and an external C compiler just to run the tests, this scheme makes them
|
||||||
|
// optional.
|
||||||
|
// +build cgo,testcgo
|
||||||
|
|
||||||
|
package testdata
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <stdint.h>
|
||||||
|
typedef unsigned char custom_uchar_t;
|
||||||
|
|
||||||
|
char *ncp = 0;
|
||||||
|
char *cp = "test";
|
||||||
|
char ca[6] = {'t', 'e', 's', 't', '2', '\0'};
|
||||||
|
unsigned char uca[6] = {'t', 'e', 's', 't', '3', '\0'};
|
||||||
|
signed char sca[6] = {'t', 'e', 's', 't', '4', '\0'};
|
||||||
|
uint8_t ui8ta[6] = {'t', 'e', 's', 't', '5', '\0'};
|
||||||
|
custom_uchar_t tuca[6] = {'t', 'e', 's', 't', '6', '\0'};
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
// GetCgoNullCharPointer returns a null char pointer via cgo. This is only
|
||||||
|
// used for tests.
|
||||||
|
func GetCgoNullCharPointer() interface{} {
|
||||||
|
return C.ncp
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoCharPointer returns a char pointer via cgo. This is only used for
|
||||||
|
// tests.
|
||||||
|
func GetCgoCharPointer() interface{} {
|
||||||
|
return C.cp
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoCharArray returns a char array via cgo and the array's len and cap.
|
||||||
|
// This is only used for tests.
|
||||||
|
func GetCgoCharArray() (interface{}, int, int) {
|
||||||
|
return C.ca, len(C.ca), cap(C.ca)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoUnsignedCharArray returns an unsigned char array via cgo and the
|
||||||
|
// array's len and cap. This is only used for tests.
|
||||||
|
func GetCgoUnsignedCharArray() (interface{}, int, int) {
|
||||||
|
return C.uca, len(C.uca), cap(C.uca)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoSignedCharArray returns a signed char array via cgo and the array's len
|
||||||
|
// and cap. This is only used for tests.
|
||||||
|
func GetCgoSignedCharArray() (interface{}, int, int) {
|
||||||
|
return C.sca, len(C.sca), cap(C.sca)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoUint8tArray returns a uint8_t array via cgo and the array's len and
|
||||||
|
// cap. This is only used for tests.
|
||||||
|
func GetCgoUint8tArray() (interface{}, int, int) {
|
||||||
|
return C.ui8ta, len(C.ui8ta), cap(C.ui8ta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoTypdefedUnsignedCharArray returns a typedefed unsigned char array via
|
||||||
|
// cgo and the array's len and cap. This is only used for tests.
|
||||||
|
func GetCgoTypdefedUnsignedCharArray() (interface{}, int, int) {
|
||||||
|
return C.tuca, len(C.tuca), cap(C.tuca)
|
||||||
|
}
|
||||||
61
vendor/github.com/davecgh/go-spew/test_coverage.txt
generated
vendored
Normal file
61
vendor/github.com/davecgh/go-spew/test_coverage.txt
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go dumpState.dump 100.00% (88/88)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.format 100.00% (82/82)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.formatPtr 100.00% (52/52)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go dumpState.dumpPtr 100.00% (44/44)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go dumpState.dumpSlice 100.00% (39/39)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go handleMethods 100.00% (30/30)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printHexPtr 100.00% (18/18)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go unsafeReflectValue 100.00% (13/13)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.constructOrigFormat 100.00% (12/12)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go fdump 100.00% (11/11)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.Format 100.00% (11/11)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go init 100.00% (10/10)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printComplex 100.00% (9/9)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go valuesSorter.Less 100.00% (8/8)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.buildDefaultFormat 100.00% (7/7)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.unpackValue 100.00% (5/5)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go dumpState.indent 100.00% (4/4)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go catchPanic 100.00% (4/4)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.convertArgs 100.00% (4/4)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go convertArgs 100.00% (4/4)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go newFormatter 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go Sdump 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printBool 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go sortValues 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Sdump 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go dumpState.unpackValue 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Printf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Println 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Sprint 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Sprintf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Sprintln 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printFloat 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go NewDefaultConfig 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printInt 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printUint 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go valuesSorter.Len 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go valuesSorter.Swap 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Errorf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprint 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprintf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprintln 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Print 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Printf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Println 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprint 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprintf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprintln 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.NewFormatter 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Fdump 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Dump 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go Fdump 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go Dump 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Fprintln 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go NewFormatter 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Errorf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Fprint 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Fprintf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Print 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew ------------------------------- 100.00% (505/505)
|
||||||
|
|
||||||
1
vendor/github.com/deepzz0/logd/.gitignore
generated
vendored
Normal file
1
vendor/github.com/deepzz0/logd/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.DS_Store
|
||||||
149
vendor/github.com/deepzz0/logd/README.md
generated
vendored
Normal file
149
vendor/github.com/deepzz0/logd/README.md
generated
vendored
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
# Logd
|
||||||
|
高性能日志系统:每日24时自动打包前一天数据,自动删除过期数据(30天),自动邮件告警。
|
||||||
|
|
||||||
|
#### Bechmark
|
||||||
|
```
|
||||||
|
BenchmarkLogFileChan-8 500000 2265 ns/op
|
||||||
|
BenchmarkLogFile-8 300000 4393 ns/op
|
||||||
|
BenchmarkStandardFile-8 1000000 2216 ns/op
|
||||||
|
|
||||||
|
BenchmarkLogFileChanMillion-8 1000000 2319 ns/op
|
||||||
|
BenchmarkLogFileMillion-8 1000000 4397 ns/op
|
||||||
|
BenchmarkStandardFileMillion-8 1000000 2232 ns/op
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Print Level
|
||||||
|
```
|
||||||
|
Ldebug = 1 << iota //
|
||||||
|
Linfo //
|
||||||
|
Lwarn //
|
||||||
|
Lerror //
|
||||||
|
Lfatal //
|
||||||
|
|
||||||
|
logd.Printf()
|
||||||
|
logd.Print()
|
||||||
|
logd.Debugf()
|
||||||
|
logd.Debug()
|
||||||
|
logd.Infof()
|
||||||
|
logd.Info()
|
||||||
|
logd.Warnf()
|
||||||
|
logd.Warn()
|
||||||
|
logd.Errorf()
|
||||||
|
logd.Error()
|
||||||
|
logd.Fatalf()
|
||||||
|
logd.Fatal()
|
||||||
|
```
|
||||||
|
5种日志等级,12种打印方法,完全理解你记录日志的需求。
|
||||||
|
|
||||||
|
```
|
||||||
|
Ldebug < Linfo < Lwarn < Lerror < Lfatal
|
||||||
|
```
|
||||||
|
通过`SetLevel()`方法设置,如:logd.SetLevel(Lwarn),系统只会打印大于等于Lwarn的日志。
|
||||||
|
|
||||||
|
> 注意:logd.Printf() 和 logd.Print() 不论等级为什么都会输出,请悉知。
|
||||||
|
|
||||||
|
#### Print Option
|
||||||
|
系统提供多种格式输出选项:
|
||||||
|
```
|
||||||
|
Lasync // 异步输出日志
|
||||||
|
Ldate // like 2006/01/02
|
||||||
|
Ltime // like 15:04:05
|
||||||
|
Lmicroseconds // like 15:04:05.123123
|
||||||
|
Llongfile // like /a/b/c/d.go:23
|
||||||
|
Lshortfile // like d.go:23
|
||||||
|
LUTC // 时间utc输出
|
||||||
|
Ldaily // 按天归档
|
||||||
|
|
||||||
|
默认提供:
|
||||||
|
LstdFlags // 2006/01/02 15:04:05.123123, /a/b/c/d.go:23
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 使用方法
|
||||||
|
1、默认方式
|
||||||
|
|
||||||
|
默认方式使用以下配置选项:
|
||||||
|
```
|
||||||
|
// 2006/01/02 15:04:05.123123, /a/b/c/d.go:23
|
||||||
|
Lall = Ldebug | Linfo | Lwarn | Lerror | Lfatal
|
||||||
|
LstdFlags = Lall | Ldate | Lmicroseconds | Lshortfile
|
||||||
|
```
|
||||||
|
这里会打印所有等级日志,打印日志格式为`2006/01/02 15:04:05.123123, /a/b/c/d.go:23`
|
||||||
|
|
||||||
|
可以通过`logd.SetLevel(level)`方式调整日志等级。`logd.SetOutput(writer)`输出日志到别的地方。例子:
|
||||||
|
```
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/deepzz0/logd"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
logd.Printf("Printf: foo\n")
|
||||||
|
logd.Print("Print: foo")
|
||||||
|
|
||||||
|
logd.Debugf("Debugf: foo\n")
|
||||||
|
logd.Debug("Debug: foo")
|
||||||
|
|
||||||
|
logd.Errorf("Errorf: foo\n")
|
||||||
|
logd.Error("Error: foo")
|
||||||
|
|
||||||
|
// 改变输出等级
|
||||||
|
logd.SetLevel(logd.Lerror)
|
||||||
|
// 不论等级如何都会输出
|
||||||
|
fmt.Println("----- 改变日志等级为Lerror -----")
|
||||||
|
logd.Printf("Printf: foo\n")
|
||||||
|
logd.Print("Print: foo")
|
||||||
|
|
||||||
|
logd.Debugf("Debugf: foo\n")
|
||||||
|
logd.Debug("Debug: foo")
|
||||||
|
|
||||||
|
logd.Errorf("Errorf: foo\n")
|
||||||
|
logd.Error("Error: foo")
|
||||||
|
|
||||||
|
fmt.Println("----- 日志等级为Lerror并写入到test.log中 -----")
|
||||||
|
f, err := os.Create("./test.log")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
logd.SetOutput(f)
|
||||||
|
|
||||||
|
logd.Printf("Printf: foo\n")
|
||||||
|
logd.Print("Print: foo")
|
||||||
|
|
||||||
|
logd.Debugf("Debugf: foo\n")
|
||||||
|
logd.Debug("Debug: foo")
|
||||||
|
|
||||||
|
logd.Errorf("Errorf: foo\n")
|
||||||
|
logd.Error("Error: foo")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
输出如下:
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
1、自定义配置
|
||||||
|
推荐需要将日志保存下来的用户使用。
|
||||||
|
|
||||||
|
```
|
||||||
|
Flags := Lwarn | Lerror | Lfatal | Ldate | Ltime | Lshortfile | Ldaily | Lasync
|
||||||
|
|
||||||
|
f, _ := os.OpenFile("testdata/onlyfile.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
|
||||||
|
log := New(LogOption{
|
||||||
|
Out: f,
|
||||||
|
Flag: Flags,
|
||||||
|
LogDir: "testdata",
|
||||||
|
ChannelLen: 1000,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
* 输出日志等级:Lwarn | Lerror | Lfatal
|
||||||
|
* 输出日志格式:Ldate | Ltime | Lshortfile
|
||||||
|
* 按日归档:Ldaily
|
||||||
|
* 异步输出:Lasync
|
||||||
|
|
||||||
|
它会将每天的日志,采用 gzip 压缩,并保留 30 天以内的日志。自动删除 30 以前的日志。
|
||||||
489
vendor/github.com/deepzz0/logd/logd.go
generated
vendored
Normal file
489
vendor/github.com/deepzz0/logd/logd.go
generated
vendored
Normal file
@@ -0,0 +1,489 @@
|
|||||||
|
package logd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Ldebug = 1 << iota //
|
||||||
|
Linfo //
|
||||||
|
Lwarn //
|
||||||
|
Lerror //
|
||||||
|
Lfatal //
|
||||||
|
Ldate // 如:2006/01/02
|
||||||
|
Ltime // 如:15:04:05
|
||||||
|
Lmicroseconds // 如:15:04:05.123123
|
||||||
|
Llongfile // 如:/a/b/c/d.go:23
|
||||||
|
Lshortfile // 如:d.go:23
|
||||||
|
LUTC // 时间utc输出
|
||||||
|
LAsync // 异步输出日志
|
||||||
|
LDaily // 按日归档,保留30天
|
||||||
|
|
||||||
|
//
|
||||||
|
// 建议标准格式为: LVEVL | FORMAT | ROTATE | ASYNC
|
||||||
|
//
|
||||||
|
Lall = Ldebug | Linfo | Lwarn | Lerror | Lfatal
|
||||||
|
|
||||||
|
// 2006/01/02 15:04:05.123123, d.go:23
|
||||||
|
LstdFlags = Lall | Ldate | Lmicroseconds | Lshortfile
|
||||||
|
// 2006/01/02 15:04:05, d.go:23
|
||||||
|
LwarnFlags = Lwarn | Lerror | Lfatal | Ldate | Ltime | Lshortfile | LDaily | LAsync
|
||||||
|
)
|
||||||
|
|
||||||
|
// 等级显示字符串
|
||||||
|
var levelMaps = map[int]string{
|
||||||
|
Ldebug: "DEBUG",
|
||||||
|
Linfo: "INFO",
|
||||||
|
Lwarn: "WARN",
|
||||||
|
Lerror: "ERROR",
|
||||||
|
Lfatal: "FATAL",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 日志结构体
|
||||||
|
type Logger struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
obj string // 打印日志对象
|
||||||
|
out io.Writer // 输出
|
||||||
|
in chan []byte // channel
|
||||||
|
dir string // 输出目录
|
||||||
|
flag int // 标志
|
||||||
|
mails Emailer // 告警邮件
|
||||||
|
}
|
||||||
|
|
||||||
|
// 日志配置项
|
||||||
|
type LogOption struct {
|
||||||
|
Out io.Writer // 输出writer
|
||||||
|
LogDir string // 日志输出目录,为空不输出到文件
|
||||||
|
ChannelLen int // channel
|
||||||
|
Flag int // 标志位
|
||||||
|
Mails Emailer // 告警邮件
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新建日志打印器
|
||||||
|
func New(option LogOption) *Logger {
|
||||||
|
wd, _ := os.Getwd()
|
||||||
|
index := strings.LastIndex(wd, "/")
|
||||||
|
logger := &Logger{
|
||||||
|
obj: wd[index+1:],
|
||||||
|
out: option.Out,
|
||||||
|
in: make(chan []byte, option.ChannelLen),
|
||||||
|
dir: option.LogDir,
|
||||||
|
flag: option.Flag,
|
||||||
|
mails: option.Mails,
|
||||||
|
}
|
||||||
|
if logger.flag|LAsync != 0 {
|
||||||
|
go logger.receive()
|
||||||
|
}
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) receive() {
|
||||||
|
today := time.Now()
|
||||||
|
var file *os.File
|
||||||
|
var err error
|
||||||
|
for data := range l.in {
|
||||||
|
if l.dir != "" && (file == nil || today.Day() != time.Now().Day()) {
|
||||||
|
l.mu.Lock()
|
||||||
|
today = time.Now()
|
||||||
|
file, err = os.OpenFile(fmt.Sprintf("%s/%s_%s.log", l.dir, l.obj, today.Format("2006-01-02")), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
l.mu.Unlock()
|
||||||
|
if l.flag&LDaily != 0 {
|
||||||
|
go l.rotate(today)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if file != nil {
|
||||||
|
file.Write(data)
|
||||||
|
}
|
||||||
|
if l.out != nil {
|
||||||
|
l.out.Write(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 压缩
|
||||||
|
func (l *Logger) rotate(t time.Time) {
|
||||||
|
filepath.Walk(l.dir, func(path string, f os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if int(t.Sub(f.ModTime()).Hours()) > 24 {
|
||||||
|
if strings.HasSuffix(f.Name(), ".log") {
|
||||||
|
cmd := exec.Command("gzip", path)
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if int(t.Sub(f.ModTime()).Hours()) > 24*30 {
|
||||||
|
if err := os.Remove(path); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 日志基本格式: date, time(hour:minute:second:microsecond), level, module, shortfile:line, <content>
|
||||||
|
func (l *Logger) Output(lvl int, calldepth int, content string) error {
|
||||||
|
_, file, line, ok := runtime.Caller(calldepth)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf []byte
|
||||||
|
l.formatHeader(&buf, lvl, time.Now(), file, line)
|
||||||
|
buf = append(buf, content...)
|
||||||
|
|
||||||
|
if l.mails != nil && lvl >= Lwarn {
|
||||||
|
go l.mails.SendMail(l.obj, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 异步输出
|
||||||
|
if l.flag&LAsync != 0 {
|
||||||
|
l.in <- buf
|
||||||
|
} else {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
|
||||||
|
l.out.Write(buf)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 整理日志header
|
||||||
|
func (l *Logger) formatHeader(buf *[]byte, lvl int, t time.Time, file string, line int) {
|
||||||
|
if l.flag&LUTC != 0 {
|
||||||
|
t = t.UTC()
|
||||||
|
}
|
||||||
|
if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 {
|
||||||
|
if l.flag&Ldate != 0 {
|
||||||
|
year, month, day := t.Date()
|
||||||
|
itoa(buf, year, 4)
|
||||||
|
*buf = append(*buf, '/')
|
||||||
|
itoa(buf, int(month), 2)
|
||||||
|
*buf = append(*buf, '/')
|
||||||
|
itoa(buf, day, 2)
|
||||||
|
*buf = append(*buf, ' ')
|
||||||
|
}
|
||||||
|
if l.flag&(Ltime|Lmicroseconds) != 0 {
|
||||||
|
hour, min, sec := t.Clock()
|
||||||
|
itoa(buf, hour, 2)
|
||||||
|
*buf = append(*buf, ':')
|
||||||
|
itoa(buf, min, 2)
|
||||||
|
*buf = append(*buf, ':')
|
||||||
|
itoa(buf, sec, 2)
|
||||||
|
if l.flag&Lmicroseconds != 0 {
|
||||||
|
*buf = append(*buf, '.')
|
||||||
|
itoa(buf, t.Nanosecond()/1e3, 6)
|
||||||
|
}
|
||||||
|
*buf = append(*buf, ' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*buf = append(*buf, getColorLevel(levelMaps[lvl])...)
|
||||||
|
*buf = append(*buf, ' ')
|
||||||
|
if l.flag&(Lshortfile|Llongfile) != 0 {
|
||||||
|
if l.flag&Lshortfile != 0 {
|
||||||
|
short := file
|
||||||
|
for i := len(file) - 1; i > 0; i-- {
|
||||||
|
if file[i] == '/' {
|
||||||
|
short = file[i+1:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file = short
|
||||||
|
}
|
||||||
|
*buf = append(*buf, file...)
|
||||||
|
*buf = append(*buf, ':')
|
||||||
|
itoa(buf, line, -1)
|
||||||
|
*buf = append(*buf, ":"...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待flush channel
|
||||||
|
func (l *Logger) WaitFlush() {
|
||||||
|
for {
|
||||||
|
if len(l.in) > 0 {
|
||||||
|
time.Sleep(time.Nanosecond * 50)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// print
|
||||||
|
func (l *Logger) Printf(format string, v ...interface{}) {
|
||||||
|
l.Output(Linfo, 2, fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Print(v ...interface{}) {
|
||||||
|
l.Output(Linfo, 2, fmt.Sprintf(smartFormat(v...), v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// debug
|
||||||
|
func (l *Logger) Debugf(format string, v ...interface{}) {
|
||||||
|
if Ldebug&l.flag != 0 {
|
||||||
|
l.Output(Ldebug, 2, fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Debug(v ...interface{}) {
|
||||||
|
if Ldebug&l.flag != 0 {
|
||||||
|
l.Output(Ldebug, 2, fmt.Sprintf(smartFormat(v...), v...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// info
|
||||||
|
func (l *Logger) Infof(format string, v ...interface{}) {
|
||||||
|
if Linfo&l.flag != 0 {
|
||||||
|
l.Output(Linfo, 2, fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Info(v ...interface{}) {
|
||||||
|
if Linfo&l.flag != 0 {
|
||||||
|
l.Output(Linfo, 2, fmt.Sprintf(smartFormat(v...), v...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// warn
|
||||||
|
func (l *Logger) Warnf(format string, v ...interface{}) {
|
||||||
|
if Lwarn&l.flag != 0 {
|
||||||
|
l.Output(Lwarn, 2, fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Warn(v ...interface{}) {
|
||||||
|
if Lwarn&l.flag != 0 {
|
||||||
|
l.Output(Lwarn, 2, fmt.Sprintf(smartFormat(v...), v...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// error
|
||||||
|
func (l *Logger) Errorf(format string, v ...interface{}) {
|
||||||
|
if Lerror&l.flag != 0 {
|
||||||
|
l.Output(Lerror, 2, fmt.Sprintf(format, v...)+CallerStack())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Error(v ...interface{}) {
|
||||||
|
if Lerror&l.flag != 0 {
|
||||||
|
l.Output(Lerror, 2, fmt.Sprintf(smartFormat(v...), v...)+CallerStack())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fatal
|
||||||
|
func (l *Logger) Fatalf(format string, v ...interface{}) {
|
||||||
|
l.Output(Lfatal, 2, fmt.Sprintf(format, v...))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Fatal(v ...interface{}) {
|
||||||
|
l.Output(Lfatal, 2, fmt.Sprintf(smartFormat(v...), v...))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Breakpoint() {
|
||||||
|
l.Output(Ldebug, 3, fmt.Sprintln("breakpoint"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置日志目录
|
||||||
|
func (l *Logger) SetLogDir(dir string) {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
l.dir = dir
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置日志对象名称
|
||||||
|
func (l *Logger) SetObj(obj string) {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
l.obj = obj
|
||||||
|
}
|
||||||
|
|
||||||
|
// 改变日志输出writer
|
||||||
|
func (l *Logger) SetOutput(out io.Writer) {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
l.out = out
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置日志等级
|
||||||
|
func (l *Logger) SetLevel(lvl int) {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
var i uint
|
||||||
|
for i = 0; i < uint(len(levelMaps)); i++ {
|
||||||
|
if lvl&1 != 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lvl >>= 1
|
||||||
|
}
|
||||||
|
l.flag = (l.flag >> i) | (Lall >> i)
|
||||||
|
l.flag <<= i
|
||||||
|
}
|
||||||
|
|
||||||
|
// standard wrapper
|
||||||
|
var Std = New(LogOption{Out: os.Stdout, Flag: LstdFlags})
|
||||||
|
|
||||||
|
func Printf(format string, v ...interface{}) {
|
||||||
|
Std.Output(Linfo, 2, fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Print(v ...interface{}) {
|
||||||
|
Std.Output(Linfo, 2, fmt.Sprintf(smartFormat(v...), v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Debugf(format string, v ...interface{}) {
|
||||||
|
if Ldebug&Std.flag != 0 {
|
||||||
|
Std.Output(Ldebug, 2, fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Debug(v ...interface{}) {
|
||||||
|
if Ldebug&Std.flag != 0 {
|
||||||
|
Std.Output(Ldebug, 2, fmt.Sprintf(smartFormat(v...), v...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Infof(format string, v ...interface{}) {
|
||||||
|
if Linfo&Std.flag != 0 {
|
||||||
|
Std.Output(Linfo, 2, fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Info(v ...interface{}) {
|
||||||
|
if Linfo&Std.flag != 0 {
|
||||||
|
Std.Output(Linfo, 2, fmt.Sprintf(smartFormat(v...), v...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Warnf(format string, v ...interface{}) {
|
||||||
|
if Lwarn&Std.flag != 0 {
|
||||||
|
Std.Output(Lwarn, 2, fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Warn(v ...interface{}) {
|
||||||
|
if Lwarn&Std.flag != 0 {
|
||||||
|
Std.Output(Lwarn, 2, fmt.Sprintf(smartFormat(v...), v...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Errorf(format string, v ...interface{}) {
|
||||||
|
if Lerror&Std.flag != 0 {
|
||||||
|
Std.Output(Lerror, 2, fmt.Sprintf(format, v...)+CallerStack())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Error(v ...interface{}) {
|
||||||
|
if Lerror&Std.flag != 0 {
|
||||||
|
Std.Output(Lerror, 2, fmt.Sprintf(smartFormat(v...), v...)+CallerStack())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fatalf(format string, v ...interface{}) {
|
||||||
|
Std.Output(Lfatal, 2, fmt.Sprintf(format, v...)+CallerStack())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fatal(v ...interface{}) {
|
||||||
|
Std.Output(Lfatal, 2, fmt.Sprintf(smartFormat(v...), v...)+CallerStack())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Breakpoint() {
|
||||||
|
Std.Breakpoint()
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetLevel(lvl int) {
|
||||||
|
Std.SetLevel(lvl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetOutput(w io.Writer) {
|
||||||
|
Std.SetOutput(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetObj(obj string) {
|
||||||
|
Std.SetObj(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
func smartFormat(v ...interface{}) string {
|
||||||
|
format := ""
|
||||||
|
for i := 0; i < len(v); i++ {
|
||||||
|
format += " %v"
|
||||||
|
}
|
||||||
|
format += "\n"
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding.
|
||||||
|
func itoa(buf *[]byte, i int, wid int) {
|
||||||
|
// Assemble decimal in reverse order.
|
||||||
|
var b [20]byte
|
||||||
|
bp := len(b) - 1
|
||||||
|
for i >= 10 || wid > 1 {
|
||||||
|
wid--
|
||||||
|
q := i / 10
|
||||||
|
b[bp] = byte('0' + i - q*10)
|
||||||
|
bp--
|
||||||
|
i = q
|
||||||
|
}
|
||||||
|
// i < 10
|
||||||
|
b[bp] = byte('0' + i)
|
||||||
|
*buf = append(*buf, b[bp:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
Gray = uint8(iota + 90)
|
||||||
|
Red
|
||||||
|
Green
|
||||||
|
Yellow
|
||||||
|
Blue
|
||||||
|
Magenta
|
||||||
|
)
|
||||||
|
|
||||||
|
// getColorLevel returns colored level string by given level.
|
||||||
|
func getColorLevel(level string) string {
|
||||||
|
level = strings.ToUpper(level)
|
||||||
|
switch level {
|
||||||
|
case "DEBUG":
|
||||||
|
return fmt.Sprintf("\033[%dm[%5s]\033[0m", Green, level)
|
||||||
|
case "INFO":
|
||||||
|
return fmt.Sprintf("\033[%dm[%5s]\033[0m", Blue, level)
|
||||||
|
case "WARN":
|
||||||
|
return fmt.Sprintf("\033[%dm[%5s]\033[0m", Magenta, level)
|
||||||
|
case "ERROR":
|
||||||
|
return fmt.Sprintf("\033[%dm[%5s]\033[0m", Yellow, level)
|
||||||
|
case "FATAL":
|
||||||
|
return fmt.Sprintf("\033[%dm[%5s]\033[0m", Red, level)
|
||||||
|
default:
|
||||||
|
return level
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CallerStack() string {
|
||||||
|
var caller_str string
|
||||||
|
for skip := 2; ; skip++ {
|
||||||
|
// 获取调用者的信息
|
||||||
|
pc, file, line, ok := runtime.Caller(skip)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
func_name := runtime.FuncForPC(pc).Name()
|
||||||
|
caller_str += "Func : " + func_name + "\nFile:" + file + ":" + fmt.Sprint(line) + "\n"
|
||||||
|
}
|
||||||
|
return caller_str
|
||||||
|
}
|
||||||
107
vendor/github.com/deepzz0/logd/logd_test.go
generated
vendored
Normal file
107
vendor/github.com/deepzz0/logd/logd_test.go
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
package logd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLog(t *testing.T) {
|
||||||
|
Printf("Printf: foo\n")
|
||||||
|
Print("Print: foo")
|
||||||
|
|
||||||
|
SetLevel(Ldebug)
|
||||||
|
|
||||||
|
Debugf("Debugf: foo\n")
|
||||||
|
Debug("Debug: foo")
|
||||||
|
|
||||||
|
Infof("Infof: foo\n")
|
||||||
|
Info("Info: foo")
|
||||||
|
|
||||||
|
Errorf("Errorf: foo\n")
|
||||||
|
Error("Error: foo")
|
||||||
|
|
||||||
|
SetLevel(Lerror)
|
||||||
|
|
||||||
|
Debugf("Debugf: foo\n")
|
||||||
|
Debug("Debug: foo")
|
||||||
|
|
||||||
|
Infof("Infof: foo\n")
|
||||||
|
Info("Info: foo")
|
||||||
|
|
||||||
|
Errorf("Errorf: foo\n")
|
||||||
|
Error("Error: foo")
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLogFileChan(b *testing.B) {
|
||||||
|
log := New(LogOption{
|
||||||
|
Flag: LAsync | Ldate | Ltime | Lshortfile,
|
||||||
|
LogDir: "testdata",
|
||||||
|
ChannelLen: 1000,
|
||||||
|
})
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
log.Print("testing this is a testing about benchmark")
|
||||||
|
}
|
||||||
|
log.WaitFlush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLogFile(b *testing.B) {
|
||||||
|
f, _ := os.OpenFile("testdata/onlyfile.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
|
||||||
|
log := New(LogOption{
|
||||||
|
Out: f,
|
||||||
|
Flag: Ldate | Ltime | Lshortfile,
|
||||||
|
LogDir: "testdata",
|
||||||
|
ChannelLen: 1000,
|
||||||
|
})
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
log.Print("testing this is a testing about benchmark")
|
||||||
|
}
|
||||||
|
log.WaitFlush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkStandardFile(b *testing.B) {
|
||||||
|
f, _ := os.OpenFile("testdata/logfile.log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
|
||||||
|
log := log.New(f, "", log.LstdFlags)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
log.Print("testing this is a testing about benchmark")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLogFileChanMillion(b *testing.B) {
|
||||||
|
log := New(LogOption{
|
||||||
|
Flag: LAsync | Ldate | Ltime | Lshortfile,
|
||||||
|
LogDir: "testdata",
|
||||||
|
ChannelLen: 1000,
|
||||||
|
})
|
||||||
|
b.N = 1000000
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
log.Print("testing this is a testing about benchmark")
|
||||||
|
}
|
||||||
|
log.WaitFlush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLogFileMillion(b *testing.B) {
|
||||||
|
f, _ := os.OpenFile("testdata/onlyfilemillion.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
|
||||||
|
log := New(LogOption{
|
||||||
|
Out: f,
|
||||||
|
Flag: Ldate | Ltime | Lshortfile,
|
||||||
|
LogDir: "testdata",
|
||||||
|
ChannelLen: 1000,
|
||||||
|
})
|
||||||
|
b.N = 1000000
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
log.Print("testing this is a testing about benchmark")
|
||||||
|
}
|
||||||
|
log.WaitFlush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkStandardFileMillion(b *testing.B) {
|
||||||
|
f, _ := os.OpenFile("testdata/logfilemillion.log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
|
||||||
|
log := log.New(f, "", log.LstdFlags)
|
||||||
|
b.N = 1000000
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
log.Print("testing this is a testing about benchmark")
|
||||||
|
}
|
||||||
|
}
|
||||||
80
vendor/github.com/deepzz0/logd/mail.go
generated
vendored
Normal file
80
vendor/github.com/deepzz0/logd/mail.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package logd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net/smtp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Emailer interface {
|
||||||
|
SendMail(fromname string, msg []byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Smtp struct {
|
||||||
|
From string // 发件箱:number@qq.com
|
||||||
|
Key string // 发件密钥:peerdmnoqirqbiaa
|
||||||
|
Host string // 主机地址:smtp.example.com
|
||||||
|
Port string // 主机端口:465
|
||||||
|
To []string // 发送给:object@163.com
|
||||||
|
Subject string // 标题:警告邮件[goblog]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Smtp) SendMail(fromname string, msg []byte) error {
|
||||||
|
// 新建连接
|
||||||
|
conn, err := tls.Dial("tcp", s.Host+":"+s.Port, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新建客户端
|
||||||
|
client, err := smtp.NewClient(conn, s.Host)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取授权
|
||||||
|
auth := smtp.PlainAuth("", s.From, s.Key, s.Host)
|
||||||
|
if err = client.Auth(auth); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 向服务器发送MAIL命令
|
||||||
|
if err = client.Mail(s.From); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备数据
|
||||||
|
str := fmt.Sprint(
|
||||||
|
"To:", strings.Join(s.To, ","),
|
||||||
|
"\r\nFrom:", fmt.Sprintf("%s<%s>", fromname, s.From),
|
||||||
|
"\r\nSubject:", s.Subject,
|
||||||
|
"\r\n", "Content-Type:text/plain;charset=UTF-8",
|
||||||
|
"\r\n\r\n",
|
||||||
|
)
|
||||||
|
data := make([]byte, len(str)+len(msg))
|
||||||
|
copy(data, []byte(str))
|
||||||
|
copy(data[len(str):], msg)
|
||||||
|
|
||||||
|
// RCPT
|
||||||
|
for _, d := range s.To {
|
||||||
|
if err := client.Rcpt(d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取WriteCloser
|
||||||
|
wc, err := client.Data()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入数据
|
||||||
|
_, err = wc.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
wc.Close()
|
||||||
|
|
||||||
|
return client.Quit()
|
||||||
|
}
|
||||||
20
vendor/github.com/deepzz0/logd/mail_test.go
generated
vendored
Normal file
20
vendor/github.com/deepzz0/logd/mail_test.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// Package logd provides ...
|
||||||
|
package logd
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestSmtpSendMail(t *testing.T) {
|
||||||
|
s := &Smtp{
|
||||||
|
From: "120735581@qq.com",
|
||||||
|
Key: "peerdmnoqirqbiaa",
|
||||||
|
Host: "smtp.qq.com",
|
||||||
|
Port: "465",
|
||||||
|
To: []string{"a120735581@foxmail.com"},
|
||||||
|
Subject: "test email from logd",
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.SendMail("test", []byte("hello world"))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
1
vendor/github.com/garyburd/redigo/.travis.yml
generated
vendored
1
vendor/github.com/garyburd/redigo/.travis.yml
generated
vendored
@@ -9,6 +9,7 @@ go:
|
|||||||
- 1.6
|
- 1.6
|
||||||
- 1.7
|
- 1.7
|
||||||
- 1.8
|
- 1.8
|
||||||
|
- 1.9
|
||||||
- tip
|
- tip
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
|||||||
1
vendor/github.com/garyburd/redigo/README.markdown
generated
vendored
1
vendor/github.com/garyburd/redigo/README.markdown
generated
vendored
@@ -21,6 +21,7 @@ Documentation
|
|||||||
|
|
||||||
- [API Reference](http://godoc.org/github.com/garyburd/redigo/redis)
|
- [API Reference](http://godoc.org/github.com/garyburd/redigo/redis)
|
||||||
- [FAQ](https://github.com/garyburd/redigo/wiki/FAQ)
|
- [FAQ](https://github.com/garyburd/redigo/wiki/FAQ)
|
||||||
|
- [Examples](https://godoc.org/github.com/garyburd/redigo/redis#pkg-examples)
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|||||||
127
vendor/github.com/garyburd/redigo/redis/conn.go
generated
vendored
127
vendor/github.com/garyburd/redigo/redis/conn.go
generated
vendored
@@ -31,7 +31,6 @@ import (
|
|||||||
|
|
||||||
// conn is the low-level implementation of Conn
|
// conn is the low-level implementation of Conn
|
||||||
type conn struct {
|
type conn struct {
|
||||||
|
|
||||||
// Shared
|
// Shared
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
pending int
|
pending int
|
||||||
@@ -73,10 +72,11 @@ type DialOption struct {
|
|||||||
type dialOptions struct {
|
type dialOptions struct {
|
||||||
readTimeout time.Duration
|
readTimeout time.Duration
|
||||||
writeTimeout time.Duration
|
writeTimeout time.Duration
|
||||||
|
dialer *net.Dialer
|
||||||
dial func(network, addr string) (net.Conn, error)
|
dial func(network, addr string) (net.Conn, error)
|
||||||
db int
|
db int
|
||||||
password string
|
password string
|
||||||
dialTLS bool
|
useTLS bool
|
||||||
skipVerify bool
|
skipVerify bool
|
||||||
tlsConfig *tls.Config
|
tlsConfig *tls.Config
|
||||||
}
|
}
|
||||||
@@ -95,17 +95,27 @@ func DialWriteTimeout(d time.Duration) DialOption {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialConnectTimeout specifies the timeout for connecting to the Redis server.
|
// DialConnectTimeout specifies the timeout for connecting to the Redis server when
|
||||||
|
// no DialNetDial option is specified.
|
||||||
func DialConnectTimeout(d time.Duration) DialOption {
|
func DialConnectTimeout(d time.Duration) DialOption {
|
||||||
return DialOption{func(do *dialOptions) {
|
return DialOption{func(do *dialOptions) {
|
||||||
dialer := net.Dialer{Timeout: d}
|
do.dialer.Timeout = d
|
||||||
do.dial = dialer.Dial
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialKeepAlive specifies the keep-alive period for TCP connections to the Redis server
|
||||||
|
// when no DialNetDial option is specified.
|
||||||
|
// If zero, keep-alives are not enabled. If no DialKeepAlive option is specified then
|
||||||
|
// the default of 5 minutes is used to ensure that half-closed TCP sessions are detected.
|
||||||
|
func DialKeepAlive(d time.Duration) DialOption {
|
||||||
|
return DialOption{func(do *dialOptions) {
|
||||||
|
do.dialer.KeepAlive = d
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialNetDial specifies a custom dial function for creating TCP
|
// DialNetDial specifies a custom dial function for creating TCP
|
||||||
// connections. If this option is left out, then net.Dial is
|
// connections, otherwise a net.Dialer customized via the other options is used.
|
||||||
// used. DialNetDial overrides DialConnectTimeout.
|
// DialNetDial overrides DialConnectTimeout and DialKeepAlive.
|
||||||
func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption {
|
func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption {
|
||||||
return DialOption{func(do *dialOptions) {
|
return DialOption{func(do *dialOptions) {
|
||||||
do.dial = dial
|
do.dial = dial
|
||||||
@@ -135,30 +145,43 @@ func DialTLSConfig(c *tls.Config) DialOption {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialTLSSkipVerify to disable server name verification when connecting
|
// DialTLSSkipVerify disables server name verification when connecting over
|
||||||
// over TLS. Has no effect when not dialing a TLS connection.
|
// TLS. Has no effect when not dialing a TLS connection.
|
||||||
func DialTLSSkipVerify(skip bool) DialOption {
|
func DialTLSSkipVerify(skip bool) DialOption {
|
||||||
return DialOption{func(do *dialOptions) {
|
return DialOption{func(do *dialOptions) {
|
||||||
do.skipVerify = skip
|
do.skipVerify = skip
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DialUseTLS specifies whether TLS should be used when connecting to the
|
||||||
|
// server. This option is ignore by DialURL.
|
||||||
|
func DialUseTLS(useTLS bool) DialOption {
|
||||||
|
return DialOption{func(do *dialOptions) {
|
||||||
|
do.useTLS = useTLS
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
// Dial connects to the Redis server at the given network and
|
// Dial connects to the Redis server at the given network and
|
||||||
// address using the specified options.
|
// address using the specified options.
|
||||||
func Dial(network, address string, options ...DialOption) (Conn, error) {
|
func Dial(network, address string, options ...DialOption) (Conn, error) {
|
||||||
do := dialOptions{
|
do := dialOptions{
|
||||||
dial: net.Dial,
|
dialer: &net.Dialer{
|
||||||
|
KeepAlive: time.Minute * 5,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option.f(&do)
|
option.f(&do)
|
||||||
}
|
}
|
||||||
|
if do.dial == nil {
|
||||||
|
do.dial = do.dialer.Dial
|
||||||
|
}
|
||||||
|
|
||||||
netConn, err := do.dial(network, address)
|
netConn, err := do.dial(network, address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if do.dialTLS {
|
if do.useTLS {
|
||||||
tlsConfig := cloneTLSClientConfig(do.tlsConfig, do.skipVerify)
|
tlsConfig := cloneTLSClientConfig(do.tlsConfig, do.skipVerify)
|
||||||
if tlsConfig.ServerName == "" {
|
if tlsConfig.ServerName == "" {
|
||||||
host, _, err := net.SplitHostPort(address)
|
host, _, err := net.SplitHostPort(address)
|
||||||
@@ -202,10 +225,6 @@ func Dial(network, address string, options ...DialOption) (Conn, error) {
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func dialTLS(do *dialOptions) {
|
|
||||||
do.dialTLS = true
|
|
||||||
}
|
|
||||||
|
|
||||||
var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`)
|
var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`)
|
||||||
|
|
||||||
// DialURL connects to a Redis server at the given URL using the Redis
|
// DialURL connects to a Redis server at the given URL using the Redis
|
||||||
@@ -257,9 +276,7 @@ func DialURL(rawurl string, options ...DialOption) (Conn, error) {
|
|||||||
return nil, fmt.Errorf("invalid database: %s", u.Path[1:])
|
return nil, fmt.Errorf("invalid database: %s", u.Path[1:])
|
||||||
}
|
}
|
||||||
|
|
||||||
if u.Scheme == "rediss" {
|
options = append(options, DialUseTLS(u.Scheme == "rediss"))
|
||||||
options = append([]DialOption{{dialTLS}}, options...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Dial("tcp", address, options...)
|
return Dial("tcp", address, options...)
|
||||||
}
|
}
|
||||||
@@ -344,39 +361,55 @@ func (c *conn) writeFloat64(n float64) error {
|
|||||||
return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64))
|
return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *conn) writeCommand(cmd string, args []interface{}) (err error) {
|
func (c *conn) writeCommand(cmd string, args []interface{}) error {
|
||||||
c.writeLen('*', 1+len(args))
|
c.writeLen('*', 1+len(args))
|
||||||
err = c.writeString(cmd)
|
if err := c.writeString(cmd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
if err != nil {
|
if err := c.writeArg(arg, true); err != nil {
|
||||||
break
|
return err
|
||||||
}
|
|
||||||
switch arg := arg.(type) {
|
|
||||||
case string:
|
|
||||||
err = c.writeString(arg)
|
|
||||||
case []byte:
|
|
||||||
err = c.writeBytes(arg)
|
|
||||||
case int:
|
|
||||||
err = c.writeInt64(int64(arg))
|
|
||||||
case int64:
|
|
||||||
err = c.writeInt64(arg)
|
|
||||||
case float64:
|
|
||||||
err = c.writeFloat64(arg)
|
|
||||||
case bool:
|
|
||||||
if arg {
|
|
||||||
err = c.writeString("1")
|
|
||||||
} else {
|
|
||||||
err = c.writeString("0")
|
|
||||||
}
|
|
||||||
case nil:
|
|
||||||
err = c.writeString("")
|
|
||||||
default:
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fmt.Fprint(&buf, arg)
|
|
||||||
err = c.writeBytes(buf.Bytes())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) writeArg(arg interface{}, argumentTypeOK bool) (err error) {
|
||||||
|
switch arg := arg.(type) {
|
||||||
|
case string:
|
||||||
|
return c.writeString(arg)
|
||||||
|
case []byte:
|
||||||
|
return c.writeBytes(arg)
|
||||||
|
case int:
|
||||||
|
return c.writeInt64(int64(arg))
|
||||||
|
case int64:
|
||||||
|
return c.writeInt64(arg)
|
||||||
|
case float64:
|
||||||
|
return c.writeFloat64(arg)
|
||||||
|
case bool:
|
||||||
|
if arg {
|
||||||
|
return c.writeString("1")
|
||||||
|
} else {
|
||||||
|
return c.writeString("0")
|
||||||
|
}
|
||||||
|
case nil:
|
||||||
|
return c.writeString("")
|
||||||
|
case Argument:
|
||||||
|
if argumentTypeOK {
|
||||||
|
return c.writeArg(arg.RedisArg(), false)
|
||||||
|
}
|
||||||
|
// See comment in default clause below.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fmt.Fprint(&buf, arg)
|
||||||
|
return c.writeBytes(buf.Bytes())
|
||||||
|
default:
|
||||||
|
// This default clause is intended to handle builtin numeric types.
|
||||||
|
// The function should return an error for other types, but this is not
|
||||||
|
// done for compatibility with previous versions of the package.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fmt.Fprint(&buf, arg)
|
||||||
|
return c.writeBytes(buf.Bytes())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type protocolError string
|
type protocolError string
|
||||||
|
|||||||
215
vendor/github.com/garyburd/redigo/redis/conn_test.go
generated
vendored
215
vendor/github.com/garyburd/redigo/redis/conn_test.go
generated
vendored
@@ -16,6 +16,9 @@ package redis_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
@@ -40,12 +43,49 @@ func (*testConn) SetDeadline(t time.Time) error { return nil }
|
|||||||
func (*testConn) SetReadDeadline(t time.Time) error { return nil }
|
func (*testConn) SetReadDeadline(t time.Time) error { return nil }
|
||||||
func (*testConn) SetWriteDeadline(t time.Time) error { return nil }
|
func (*testConn) SetWriteDeadline(t time.Time) error { return nil }
|
||||||
|
|
||||||
func dialTestConn(r io.Reader, w io.Writer) redis.DialOption {
|
func dialTestConn(r string, w io.Writer) redis.DialOption {
|
||||||
return redis.DialNetDial(func(net, addr string) (net.Conn, error) {
|
return redis.DialNetDial(func(network, addr string) (net.Conn, error) {
|
||||||
return &testConn{Reader: r, Writer: w}, nil
|
return &testConn{Reader: strings.NewReader(r), Writer: w}, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type tlsTestConn struct {
|
||||||
|
net.Conn
|
||||||
|
done chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *tlsTestConn) Close() error {
|
||||||
|
c.Conn.Close()
|
||||||
|
<-c.done
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dialTestConnTLS(r string, w io.Writer) redis.DialOption {
|
||||||
|
return redis.DialNetDial(func(network, addr string) (net.Conn, error) {
|
||||||
|
client, server := net.Pipe()
|
||||||
|
tlsServer := tls.Server(server, &serverTLSConfig)
|
||||||
|
go io.Copy(tlsServer, strings.NewReader(r))
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
io.Copy(w, tlsServer)
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
return &tlsTestConn{Conn: client, done: done}, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type durationArg struct {
|
||||||
|
time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t durationArg) RedisArg() interface{} {
|
||||||
|
return t.Seconds()
|
||||||
|
}
|
||||||
|
|
||||||
|
type recursiveArg int
|
||||||
|
|
||||||
|
func (v recursiveArg) RedisArg() interface{} { return v }
|
||||||
|
|
||||||
var writeTests = []struct {
|
var writeTests = []struct {
|
||||||
args []interface{}
|
args []interface{}
|
||||||
expected string
|
expected string
|
||||||
@@ -82,6 +122,14 @@ var writeTests = []struct {
|
|||||||
[]interface{}{"SET", "key", nil},
|
[]interface{}{"SET", "key", nil},
|
||||||
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$0\r\n\r\n",
|
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$0\r\n\r\n",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
[]interface{}{"SET", "key", durationArg{time.Minute}},
|
||||||
|
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$2\r\n60\r\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]interface{}{"SET", "key", recursiveArg(123)},
|
||||||
|
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n123\r\n",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
[]interface{}{"ECHO", true, false},
|
[]interface{}{"ECHO", true, false},
|
||||||
"*3\r\n$4\r\nECHO\r\n$1\r\n1\r\n$1\r\n0\r\n",
|
"*3\r\n$4\r\nECHO\r\n$1\r\n1\r\n$1\r\n0\r\n",
|
||||||
@@ -91,7 +139,7 @@ var writeTests = []struct {
|
|||||||
func TestWrite(t *testing.T) {
|
func TestWrite(t *testing.T) {
|
||||||
for _, tt := range writeTests {
|
for _, tt := range writeTests {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
c, _ := redis.Dial("", "", dialTestConn(nil, &buf))
|
c, _ := redis.Dial("", "", dialTestConn("", &buf))
|
||||||
err := c.Send(tt.args[0].(string), tt.args[1:]...)
|
err := c.Send(tt.args[0].(string), tt.args[1:]...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Send(%v) returned error %v", tt.args, err)
|
t.Errorf("Send(%v) returned error %v", tt.args, err)
|
||||||
@@ -190,7 +238,7 @@ var readTests = []struct {
|
|||||||
|
|
||||||
func TestRead(t *testing.T) {
|
func TestRead(t *testing.T) {
|
||||||
for _, tt := range readTests {
|
for _, tt := range readTests {
|
||||||
c, _ := redis.Dial("", "", dialTestConn(strings.NewReader(tt.reply), nil))
|
c, _ := redis.Dial("", "", dialTestConn(tt.reply, nil))
|
||||||
actual, err := c.Receive()
|
actual, err := c.Receive()
|
||||||
if tt.expected == errorSentinel {
|
if tt.expected == errorSentinel {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -447,7 +495,7 @@ var dialErrors = []struct {
|
|||||||
"localhost",
|
"localhost",
|
||||||
"invalid redis URL scheme",
|
"invalid redis URL scheme",
|
||||||
},
|
},
|
||||||
// The error message for invalid hosts is diffferent in different
|
// The error message for invalid hosts is different in different
|
||||||
// versions of Go, so just check that there is an error message.
|
// versions of Go, so just check that there is an error message.
|
||||||
{
|
{
|
||||||
"redis://weird url",
|
"redis://weird url",
|
||||||
@@ -502,41 +550,85 @@ func TestDialURLHost(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDialURLPassword(t *testing.T) {
|
var dialURLTests = []struct {
|
||||||
var buf bytes.Buffer
|
description string
|
||||||
_, err := redis.DialURL("redis://x:abc123@localhost", dialTestConn(strings.NewReader("+OK\r\n"), &buf))
|
url string
|
||||||
if err != nil {
|
r string
|
||||||
t.Error("dial error:", err)
|
w string
|
||||||
|
}{
|
||||||
|
{"password", "redis://x:abc123@localhost", "+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"},
|
||||||
|
{"database 3", "redis://localhost/3", "+OK\r\n", "*2\r\n$6\r\nSELECT\r\n$1\r\n3\r\n"},
|
||||||
|
{"database 99", "redis://localhost/99", "+OK\r\n", "*2\r\n$6\r\nSELECT\r\n$2\r\n99\r\n"},
|
||||||
|
{"no database", "redis://localhost/", "+OK\r\n", ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDialURL(t *testing.T) {
|
||||||
|
for _, tt := range dialURLTests {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
// UseTLS should be ignored in all of these tests.
|
||||||
|
_, err := redis.DialURL(tt.url, dialTestConn(tt.r, &buf), redis.DialUseTLS(true))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s dial error: %v", tt.description, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if w := buf.String(); w != tt.w {
|
||||||
|
t.Errorf("%s commands = %q, want %q", tt.description, w, tt.w)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
expected := "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"
|
}
|
||||||
|
|
||||||
|
func checkPingPong(t *testing.T, buf *bytes.Buffer, c redis.Conn) {
|
||||||
|
resp, err := c.Do("PING")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("ping error:", err)
|
||||||
|
}
|
||||||
|
// Close connection to ensure that writes to buf are complete.
|
||||||
|
c.Close()
|
||||||
|
expected := "*1\r\n$4\r\nPING\r\n"
|
||||||
actual := buf.String()
|
actual := buf.String()
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
t.Errorf("commands = %q, want %q", actual, expected)
|
t.Errorf("commands = %q, want %q", actual, expected)
|
||||||
}
|
}
|
||||||
|
if resp != "PONG" {
|
||||||
|
t.Errorf("resp = %v, want %v", resp, "PONG")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDialURLDatabase(t *testing.T) {
|
const pingResponse = "+PONG\r\n"
|
||||||
var buf3 bytes.Buffer
|
|
||||||
_, err3 := redis.DialURL("redis://localhost/3", dialTestConn(strings.NewReader("+OK\r\n"), &buf3))
|
func TestDialURLTLS(t *testing.T) {
|
||||||
if err3 != nil {
|
var buf bytes.Buffer
|
||||||
t.Error("dial error:", err3)
|
c, err := redis.DialURL("rediss://example.com/",
|
||||||
|
redis.DialTLSConfig(&clientTLSConfig),
|
||||||
|
dialTestConnTLS(pingResponse, &buf))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("dial error:", err)
|
||||||
}
|
}
|
||||||
expected3 := "*2\r\n$6\r\nSELECT\r\n$1\r\n3\r\n"
|
checkPingPong(t, &buf, c)
|
||||||
actual3 := buf3.String()
|
}
|
||||||
if actual3 != expected3 {
|
|
||||||
t.Errorf("commands = %q, want %q", actual3, expected3)
|
func TestDialUseTLS(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
c, err := redis.Dial("tcp", "example.com:6379",
|
||||||
|
redis.DialTLSConfig(&clientTLSConfig),
|
||||||
|
dialTestConnTLS(pingResponse, &buf),
|
||||||
|
redis.DialUseTLS(true))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("dial error:", err)
|
||||||
}
|
}
|
||||||
// empty DB means 0
|
checkPingPong(t, &buf, c)
|
||||||
var buf0 bytes.Buffer
|
}
|
||||||
_, err0 := redis.DialURL("redis://localhost/", dialTestConn(strings.NewReader("+OK\r\n"), &buf0))
|
|
||||||
if err0 != nil {
|
func TestDialTLSSKipVerify(t *testing.T) {
|
||||||
t.Error("dial error:", err0)
|
var buf bytes.Buffer
|
||||||
}
|
c, err := redis.Dial("tcp", "example.com:6379",
|
||||||
expected0 := ""
|
dialTestConnTLS(pingResponse, &buf),
|
||||||
actual0 := buf0.String()
|
redis.DialTLSSkipVerify(true),
|
||||||
if actual0 != expected0 {
|
redis.DialUseTLS(true))
|
||||||
t.Errorf("commands = %q, want %q", actual0, expected0)
|
if err != nil {
|
||||||
|
t.Fatal("dial error:", err)
|
||||||
}
|
}
|
||||||
|
checkPingPong(t, &buf, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect to local instance of Redis running on the default port.
|
// Connect to local instance of Redis running on the default port.
|
||||||
@@ -668,3 +760,64 @@ func BenchmarkDoPing(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var clientTLSConfig, serverTLSConfig tls.Config
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// The certificate and key for testing TLS dial options was created
|
||||||
|
// using the command
|
||||||
|
//
|
||||||
|
// go run GOROOT/src/crypto/tls/generate_cert.go \
|
||||||
|
// --rsa-bits 1024 \
|
||||||
|
// --host 127.0.0.1,::1,example.com --ca \
|
||||||
|
// --start-date "Jan 1 00:00:00 1970" \
|
||||||
|
// --duration=1000000h
|
||||||
|
//
|
||||||
|
// where GOROOT is the value of GOROOT reported by go env.
|
||||||
|
localhostCert := []byte(`
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICFDCCAX2gAwIBAgIRAJfBL4CUxkXcdlFurb3K+iowDQYJKoZIhvcNAQELBQAw
|
||||||
|
EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
|
||||||
|
MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
|
||||||
|
gYkCgYEArizw8WxMUQ3bGHLeuJ4fDrEpy+L2pqrbYRlKk1DasJ/VkB8bImzIpe6+
|
||||||
|
LGjiYIxvnDCOJ3f3QplcQuiuMyl6f2irJlJsbFT8Lo/3obnuTKAIaqUdJUqBg6y+
|
||||||
|
JaL8Auk97FvunfKFv8U1AIhgiLzAfQ/3Eaq1yi87Ra6pMjGbTtcCAwEAAaNoMGYw
|
||||||
|
DgYDVR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQF
|
||||||
|
MAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAEwDQYJKoZIhvcNAQELBQADgYEAdZ8daIVkyhVwflt5I19m0oq1TycbGO1+
|
||||||
|
ach7T6cZiBQeNR/SJtxr/wKPEpmvUgbv2BfFrKJ8QoIHYsbNSURTWSEa02pfw4k9
|
||||||
|
6RQhij3ZkG79Ituj5OYRORV6Z0HUW32r670BtcuHuAhq7YA6Nxy4FtSt7bAlVdRt
|
||||||
|
rrKgNsltzMk=
|
||||||
|
-----END CERTIFICATE-----`)
|
||||||
|
|
||||||
|
localhostKey := []byte(`
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIICXAIBAAKBgQCuLPDxbExRDdsYct64nh8OsSnL4vamqtthGUqTUNqwn9WQHxsi
|
||||||
|
bMil7r4saOJgjG+cMI4nd/dCmVxC6K4zKXp/aKsmUmxsVPwuj/ehue5MoAhqpR0l
|
||||||
|
SoGDrL4lovwC6T3sW+6d8oW/xTUAiGCIvMB9D/cRqrXKLztFrqkyMZtO1wIDAQAB
|
||||||
|
AoGACrc5G6FOEK6JjDeE/Fa+EmlT6PdNtXNNi+vCas3Opo8u1G8VfEi1D4BgstrB
|
||||||
|
Eq+RLkrOdB8tVyuYQYWPMhabMqF+hhKJN72j0OwfuPlVvTInwb/cKjo/zbH1IA+Y
|
||||||
|
HenHNK4ywv7/p/9/MvQPJ3I32cQBCgGUW5chVSH5M1sj5gECQQDabQAI1X0uDqCm
|
||||||
|
KbX9gXVkAgxkFddrt6LBHt57xujFcqEKFE7nwKhDh7DweVs/VEJ+kpid4z+UnLOw
|
||||||
|
KjtP9JolAkEAzCNBphQ//IsbH5rNs10wIUw3Ks/Oepicvr6kUFbIv+neRzi1iJHa
|
||||||
|
m6H7EayK3PWgax6BAsR/t0Jc9XV7r2muSwJAVzN09BHnK+ADGtNEKLTqXMbEk6B0
|
||||||
|
pDhn7ZmZUOkUPN+Kky+QYM11X6Bob1jDqQDGmymDbGUxGO+GfSofC8inUQJAGfci
|
||||||
|
Eo3g1a6b9JksMPRZeuLG4ZstGErxJRH6tH1Va5PDwitka8qhk8o2tTjNMO3NSdLH
|
||||||
|
diKoXBcE2/Pll5pJoQJBAIMiiMIzXJhnN4mX8may44J/HvMlMf2xuVH2gNMwmZuc
|
||||||
|
Bjqn3yoLHaoZVvbWOi0C2TCN4FjXjaLNZGifQPbIcaA=
|
||||||
|
-----END RSA PRIVATE KEY-----`)
|
||||||
|
|
||||||
|
cert, err := tls.X509KeyPair(localhostCert, localhostKey)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("error creating key pair: %v", err))
|
||||||
|
}
|
||||||
|
serverTLSConfig.Certificates = []tls.Certificate{cert}
|
||||||
|
|
||||||
|
certificate, err := x509.ParseCertificate(serverTLSConfig.Certificates[0].Certificate[0])
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("error parsing x509 certificate: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
clientTLSConfig.RootCAs = x509.NewCertPool()
|
||||||
|
clientTLSConfig.RootCAs.AddCert(certificate)
|
||||||
|
}
|
||||||
|
|||||||
4
vendor/github.com/garyburd/redigo/redis/doc.go
generated
vendored
4
vendor/github.com/garyburd/redigo/redis/doc.go
generated
vendored
@@ -38,7 +38,7 @@
|
|||||||
//
|
//
|
||||||
// n, err := conn.Do("APPEND", "key", "value")
|
// n, err := conn.Do("APPEND", "key", "value")
|
||||||
//
|
//
|
||||||
// The Do method converts command arguments to binary strings for transmission
|
// The Do method converts command arguments to bulk strings for transmission
|
||||||
// to the server as follows:
|
// to the server as follows:
|
||||||
//
|
//
|
||||||
// Go Type Conversion
|
// Go Type Conversion
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
// float64 strconv.FormatFloat(v, 'g', -1, 64)
|
// float64 strconv.FormatFloat(v, 'g', -1, 64)
|
||||||
// bool true -> "1", false -> "0"
|
// bool true -> "1", false -> "0"
|
||||||
// nil ""
|
// nil ""
|
||||||
// all other types fmt.Print(v)
|
// all other types fmt.Fprint(w, v)
|
||||||
//
|
//
|
||||||
// Redis command reply types are represented using the following Go types:
|
// Redis command reply types are represented using the following Go types:
|
||||||
//
|
//
|
||||||
|
|||||||
22
vendor/github.com/garyburd/redigo/redis/pool.go
generated
vendored
22
vendor/github.com/garyburd/redigo/redis/pool.go
generated
vendored
@@ -115,7 +115,6 @@ var (
|
|||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
type Pool struct {
|
type Pool struct {
|
||||||
|
|
||||||
// Dial is an application supplied function for creating and configuring a
|
// Dial is an application supplied function for creating and configuring a
|
||||||
// connection.
|
// connection.
|
||||||
//
|
//
|
||||||
@@ -181,6 +180,26 @@ func (p *Pool) Get() Conn {
|
|||||||
return &pooledConnection{p: p, c: c}
|
return &pooledConnection{p: p, c: c}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PoolStats contains pool statistics.
|
||||||
|
type PoolStats struct {
|
||||||
|
// ActiveCount is the number of connections in the pool. The count includes idle connections and connections in use.
|
||||||
|
ActiveCount int
|
||||||
|
// IdleCount is the number of idle connections in the pool.
|
||||||
|
IdleCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stats returns pool's statistics.
|
||||||
|
func (p *Pool) Stats() PoolStats {
|
||||||
|
p.mu.Lock()
|
||||||
|
stats := PoolStats{
|
||||||
|
ActiveCount: p.active,
|
||||||
|
IdleCount: p.idle.Len(),
|
||||||
|
}
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
return stats
|
||||||
|
}
|
||||||
|
|
||||||
// ActiveCount returns the number of connections in the pool. The count includes idle connections and connections in use.
|
// ActiveCount returns the number of connections in the pool. The count includes idle connections and connections in use.
|
||||||
func (p *Pool) ActiveCount() int {
|
func (p *Pool) ActiveCount() int {
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
@@ -249,7 +268,6 @@ func (p *Pool) get() (Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|
||||||
// Get idle connection.
|
// Get idle connection.
|
||||||
|
|
||||||
for i, n := 0, p.idle.Len(); i < n; i++ {
|
for i, n := 0, p.idle.Len(); i < n; i++ {
|
||||||
|
|||||||
13
vendor/github.com/garyburd/redigo/redis/pool_test.go
generated
vendored
13
vendor/github.com/garyburd/redigo/redis/pool_test.go
generated
vendored
@@ -92,12 +92,15 @@ func (d *poolDialer) check(message string, p *redis.Pool, dialed, open, inuse in
|
|||||||
d.t.Errorf("%s: open=%d, want %d", message, d.open, open)
|
d.t.Errorf("%s: open=%d, want %d", message, d.open, open)
|
||||||
}
|
}
|
||||||
|
|
||||||
if active := p.ActiveCount(); active != open {
|
stats := p.Stats()
|
||||||
d.t.Errorf("%s: active=%d, want %d", message, active, open)
|
|
||||||
|
if stats.ActiveCount != open {
|
||||||
|
d.t.Errorf("%s: active=%d, want %d", message, stats.ActiveCount, open)
|
||||||
}
|
}
|
||||||
if idle := p.IdleCount(); idle != open-inuse {
|
if stats.IdleCount != open-inuse {
|
||||||
d.t.Errorf("%s: idle=%d, want %d", message, idle, open-inuse)
|
d.t.Errorf("%s: idle=%d, want %d", message, stats.IdleCount, open-inuse)
|
||||||
}
|
}
|
||||||
|
|
||||||
d.mu.Unlock()
|
d.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,8 +440,8 @@ func startGoroutines(p *redis.Pool, cmd string, args ...interface{}) chan error
|
|||||||
go func() {
|
go func() {
|
||||||
c := p.Get()
|
c := p.Get()
|
||||||
_, err := c.Do(cmd, args...)
|
_, err := c.Do(cmd, args...)
|
||||||
errs <- err
|
|
||||||
c.Close()
|
c.Close()
|
||||||
|
errs <- err
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
6
vendor/github.com/garyburd/redigo/redis/pubsub.go
generated
vendored
6
vendor/github.com/garyburd/redigo/redis/pubsub.go
generated
vendored
@@ -18,7 +18,6 @@ import "errors"
|
|||||||
|
|
||||||
// Subscription represents a subscribe or unsubscribe notification.
|
// Subscription represents a subscribe or unsubscribe notification.
|
||||||
type Subscription struct {
|
type Subscription struct {
|
||||||
|
|
||||||
// Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe"
|
// Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe"
|
||||||
Kind string
|
Kind string
|
||||||
|
|
||||||
@@ -31,7 +30,6 @@ type Subscription struct {
|
|||||||
|
|
||||||
// Message represents a message notification.
|
// Message represents a message notification.
|
||||||
type Message struct {
|
type Message struct {
|
||||||
|
|
||||||
// The originating channel.
|
// The originating channel.
|
||||||
Channel string
|
Channel string
|
||||||
|
|
||||||
@@ -41,7 +39,6 @@ type Message struct {
|
|||||||
|
|
||||||
// PMessage represents a pmessage notification.
|
// PMessage represents a pmessage notification.
|
||||||
type PMessage struct {
|
type PMessage struct {
|
||||||
|
|
||||||
// The matched pattern.
|
// The matched pattern.
|
||||||
Pattern string
|
Pattern string
|
||||||
|
|
||||||
@@ -94,6 +91,9 @@ func (c PubSubConn) PUnsubscribe(channel ...interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ping sends a PING to the server with the specified data.
|
// Ping sends a PING to the server with the specified data.
|
||||||
|
//
|
||||||
|
// The connection must be subscribed to at least one channel or pattern when
|
||||||
|
// calling this method.
|
||||||
func (c PubSubConn) Ping(data string) error {
|
func (c PubSubConn) Ping(data string) error {
|
||||||
c.Conn.Send("PING", data)
|
c.Conn.Send("PING", data)
|
||||||
return c.Conn.Flush()
|
return c.Conn.Flush()
|
||||||
|
|||||||
20
vendor/github.com/garyburd/redigo/redis/redis.go
generated
vendored
20
vendor/github.com/garyburd/redigo/redis/redis.go
generated
vendored
@@ -39,3 +39,23 @@ type Conn interface {
|
|||||||
// Receive receives a single reply from the Redis server
|
// Receive receives a single reply from the Redis server
|
||||||
Receive() (reply interface{}, err error)
|
Receive() (reply interface{}, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Argument is the interface implemented by an object which wants to control how
|
||||||
|
// the object is converted to Redis bulk strings.
|
||||||
|
type Argument interface {
|
||||||
|
// RedisArg returns a value to be encoded as a bulk string per the
|
||||||
|
// conversions listed in the section 'Executing Commands'.
|
||||||
|
// Implementations should typically return a []byte or string.
|
||||||
|
RedisArg() interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scanner is implemented by an object which wants to control its value is
|
||||||
|
// interpreted when read from Redis.
|
||||||
|
type Scanner interface {
|
||||||
|
// RedisScan assigns a value from a Redis value. The argument src is one of
|
||||||
|
// the reply types listed in the section `Executing Commands`.
|
||||||
|
//
|
||||||
|
// An error should be returned if the value cannot be stored without
|
||||||
|
// loss of information.
|
||||||
|
RedisScan(src interface{}) error
|
||||||
|
}
|
||||||
|
|||||||
170
vendor/github.com/garyburd/redigo/redis/reply.go
generated
vendored
170
vendor/github.com/garyburd/redigo/redis/reply.go
generated
vendored
@@ -243,34 +243,67 @@ func Values(reply interface{}, err error) ([]interface{}, error) {
|
|||||||
return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply)
|
return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sliceHelper(reply interface{}, err error, name string, makeSlice func(int), assign func(int, interface{}) error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch reply := reply.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
makeSlice(len(reply))
|
||||||
|
for i := range reply {
|
||||||
|
if reply[i] == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := assign(i, reply[i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case nil:
|
||||||
|
return ErrNil
|
||||||
|
case Error:
|
||||||
|
return reply
|
||||||
|
}
|
||||||
|
return fmt.Errorf("redigo: unexpected type for %s, got type %T", name, reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64s is a helper that converts an array command reply to a []float64. If
|
||||||
|
// err is not equal to nil, then Float64s returns nil, err. Nil array items are
|
||||||
|
// converted to 0 in the output slice. Floats64 returns an error if an array
|
||||||
|
// item is not a bulk string or nil.
|
||||||
|
func Float64s(reply interface{}, err error) ([]float64, error) {
|
||||||
|
var result []float64
|
||||||
|
err = sliceHelper(reply, err, "Float64s", func(n int) { result = make([]float64, n) }, func(i int, v interface{}) error {
|
||||||
|
p, ok := v.([]byte)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("redigo: unexpected element type for Floats64, got type %T", v)
|
||||||
|
}
|
||||||
|
f, err := strconv.ParseFloat(string(p), 64)
|
||||||
|
result[i] = f
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
// Strings is a helper that converts an array command reply to a []string. If
|
// Strings is a helper that converts an array command reply to a []string. If
|
||||||
// err is not equal to nil, then Strings returns nil, err. Nil array items are
|
// err is not equal to nil, then Strings returns nil, err. Nil array items are
|
||||||
// converted to "" in the output slice. Strings returns an error if an array
|
// converted to "" in the output slice. Strings returns an error if an array
|
||||||
// item is not a bulk string or nil.
|
// item is not a bulk string or nil.
|
||||||
func Strings(reply interface{}, err error) ([]string, error) {
|
func Strings(reply interface{}, err error) ([]string, error) {
|
||||||
if err != nil {
|
var result []string
|
||||||
return nil, err
|
err = sliceHelper(reply, err, "Strings", func(n int) { result = make([]string, n) }, func(i int, v interface{}) error {
|
||||||
}
|
switch v := v.(type) {
|
||||||
switch reply := reply.(type) {
|
case string:
|
||||||
case []interface{}:
|
result[i] = v
|
||||||
result := make([]string, len(reply))
|
return nil
|
||||||
for i := range reply {
|
case []byte:
|
||||||
if reply[i] == nil {
|
result[i] = string(v)
|
||||||
continue
|
return nil
|
||||||
}
|
default:
|
||||||
p, ok := reply[i].([]byte)
|
return fmt.Errorf("redigo: unexpected element type for Strings, got type %T", v)
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("redigo: unexpected element type for Strings, got type %T", reply[i])
|
|
||||||
}
|
|
||||||
result[i] = string(p)
|
|
||||||
}
|
}
|
||||||
return result, nil
|
})
|
||||||
case nil:
|
return result, err
|
||||||
return nil, ErrNil
|
|
||||||
case Error:
|
|
||||||
return nil, reply
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("redigo: unexpected type for Strings, got type %T", reply)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ByteSlices is a helper that converts an array command reply to a [][]byte.
|
// ByteSlices is a helper that converts an array command reply to a [][]byte.
|
||||||
@@ -278,43 +311,64 @@ func Strings(reply interface{}, err error) ([]string, error) {
|
|||||||
// items are stay nil. ByteSlices returns an error if an array item is not a
|
// items are stay nil. ByteSlices returns an error if an array item is not a
|
||||||
// bulk string or nil.
|
// bulk string or nil.
|
||||||
func ByteSlices(reply interface{}, err error) ([][]byte, error) {
|
func ByteSlices(reply interface{}, err error) ([][]byte, error) {
|
||||||
if err != nil {
|
var result [][]byte
|
||||||
return nil, err
|
err = sliceHelper(reply, err, "ByteSlices", func(n int) { result = make([][]byte, n) }, func(i int, v interface{}) error {
|
||||||
}
|
p, ok := v.([]byte)
|
||||||
switch reply := reply.(type) {
|
if !ok {
|
||||||
case []interface{}:
|
return fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", v)
|
||||||
result := make([][]byte, len(reply))
|
|
||||||
for i := range reply {
|
|
||||||
if reply[i] == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
p, ok := reply[i].([]byte)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", reply[i])
|
|
||||||
}
|
|
||||||
result[i] = p
|
|
||||||
}
|
}
|
||||||
return result, nil
|
result[i] = p
|
||||||
case nil:
|
return nil
|
||||||
return nil, ErrNil
|
})
|
||||||
case Error:
|
return result, err
|
||||||
return nil, reply
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("redigo: unexpected type for ByteSlices, got type %T", reply)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ints is a helper that converts an array command reply to a []int. If
|
// Int64s is a helper that converts an array command reply to a []int64.
|
||||||
// err is not equal to nil, then Ints returns nil, err.
|
// If err is not equal to nil, then Int64s returns nil, err. Nil array
|
||||||
|
// items are stay nil. Int64s returns an error if an array item is not a
|
||||||
|
// bulk string or nil.
|
||||||
|
func Int64s(reply interface{}, err error) ([]int64, error) {
|
||||||
|
var result []int64
|
||||||
|
err = sliceHelper(reply, err, "Int64s", func(n int) { result = make([]int64, n) }, func(i int, v interface{}) error {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case int64:
|
||||||
|
result[i] = v
|
||||||
|
return nil
|
||||||
|
case []byte:
|
||||||
|
n, err := strconv.ParseInt(string(v), 10, 64)
|
||||||
|
result[i] = n
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("redigo: unexpected element type for Int64s, got type %T", v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ints is a helper that converts an array command reply to a []in.
|
||||||
|
// If err is not equal to nil, then Ints returns nil, err. Nil array
|
||||||
|
// items are stay nil. Ints returns an error if an array item is not a
|
||||||
|
// bulk string or nil.
|
||||||
func Ints(reply interface{}, err error) ([]int, error) {
|
func Ints(reply interface{}, err error) ([]int, error) {
|
||||||
var ints []int
|
var result []int
|
||||||
values, err := Values(reply, err)
|
err = sliceHelper(reply, err, "Ints", func(n int) { result = make([]int, n) }, func(i int, v interface{}) error {
|
||||||
if err != nil {
|
switch v := v.(type) {
|
||||||
return ints, err
|
case int64:
|
||||||
}
|
n := int(v)
|
||||||
if err := ScanSlice(values, &ints); err != nil {
|
if int64(n) != v {
|
||||||
return ints, err
|
return strconv.ErrRange
|
||||||
}
|
}
|
||||||
return ints, nil
|
result[i] = n
|
||||||
|
return nil
|
||||||
|
case []byte:
|
||||||
|
n, err := strconv.Atoi(string(v))
|
||||||
|
result[i] = n
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("redigo: unexpected element type for Ints, got type %T", v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// StringMap is a helper that converts an array of strings (alternating key, value)
|
// StringMap is a helper that converts an array of strings (alternating key, value)
|
||||||
@@ -333,7 +387,7 @@ func StringMap(result interface{}, err error) (map[string]string, error) {
|
|||||||
key, okKey := values[i].([]byte)
|
key, okKey := values[i].([]byte)
|
||||||
value, okValue := values[i+1].([]byte)
|
value, okValue := values[i+1].([]byte)
|
||||||
if !okKey || !okValue {
|
if !okKey || !okValue {
|
||||||
return nil, errors.New("redigo: ScanMap key not a bulk string value")
|
return nil, errors.New("redigo: StringMap key not a bulk string value")
|
||||||
}
|
}
|
||||||
m[string(key)] = string(value)
|
m[string(key)] = string(value)
|
||||||
}
|
}
|
||||||
@@ -355,7 +409,7 @@ func IntMap(result interface{}, err error) (map[string]int, error) {
|
|||||||
for i := 0; i < len(values); i += 2 {
|
for i := 0; i < len(values); i += 2 {
|
||||||
key, ok := values[i].([]byte)
|
key, ok := values[i].([]byte)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("redigo: ScanMap key not a bulk string value")
|
return nil, errors.New("redigo: IntMap key not a bulk string value")
|
||||||
}
|
}
|
||||||
value, err := Int(values[i+1], nil)
|
value, err := Int(values[i+1], nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -381,7 +435,7 @@ func Int64Map(result interface{}, err error) (map[string]int64, error) {
|
|||||||
for i := 0; i < len(values); i += 2 {
|
for i := 0; i < len(values); i += 2 {
|
||||||
key, ok := values[i].([]byte)
|
key, ok := values[i].([]byte)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("redigo: ScanMap key not a bulk string value")
|
return nil, errors.New("redigo: Int64Map key not a bulk string value")
|
||||||
}
|
}
|
||||||
value, err := Int64(values[i+1], nil)
|
value, err := Int64(values[i+1], nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
36
vendor/github.com/garyburd/redigo/redis/reply_test.go
generated
vendored
36
vendor/github.com/garyburd/redigo/redis/reply_test.go
generated
vendored
@@ -37,24 +37,44 @@ var replyTests = []struct {
|
|||||||
expected valueError
|
expected valueError
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"ints([v1, v2])",
|
"ints([[]byte, []byte])",
|
||||||
ve(redis.Ints([]interface{}{[]byte("4"), []byte("5")}, nil)),
|
ve(redis.Ints([]interface{}{[]byte("4"), []byte("5")}, nil)),
|
||||||
ve([]int{4, 5}, nil),
|
ve([]int{4, 5}, nil),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ints([nt64, int64])",
|
||||||
|
ve(redis.Ints([]interface{}{int64(4), int64(5)}, nil)),
|
||||||
|
ve([]int{4, 5}, nil),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ints([[]byte, nil, []byte])",
|
||||||
|
ve(redis.Ints([]interface{}{[]byte("4"), nil, []byte("5")}, nil)),
|
||||||
|
ve([]int{4, 0, 5}, nil),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ints(nil)",
|
"ints(nil)",
|
||||||
ve(redis.Ints(nil, nil)),
|
ve(redis.Ints(nil, nil)),
|
||||||
ve([]int(nil), redis.ErrNil),
|
ve([]int(nil), redis.ErrNil),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"strings([v1, v2])",
|
"int64s([[]byte, []byte])",
|
||||||
|
ve(redis.Int64s([]interface{}{[]byte("4"), []byte("5")}, nil)),
|
||||||
|
ve([]int64{4, 5}, nil),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"int64s([int64, int64])",
|
||||||
|
ve(redis.Int64s([]interface{}{int64(4), int64(5)}, nil)),
|
||||||
|
ve([]int64{4, 5}, nil),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"strings([[]byte, []bytev2])",
|
||||||
ve(redis.Strings([]interface{}{[]byte("v1"), []byte("v2")}, nil)),
|
ve(redis.Strings([]interface{}{[]byte("v1"), []byte("v2")}, nil)),
|
||||||
ve([]string{"v1", "v2"}, nil),
|
ve([]string{"v1", "v2"}, nil),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"strings(nil)",
|
"strings([string, string])",
|
||||||
ve(redis.Strings(nil, nil)),
|
ve(redis.Strings([]interface{}{"v1", "v2"}, nil)),
|
||||||
ve([]string(nil), redis.ErrNil),
|
ve([]string{"v1", "v2"}, nil),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"byteslices([v1, v2])",
|
"byteslices([v1, v2])",
|
||||||
@@ -62,9 +82,9 @@ var replyTests = []struct {
|
|||||||
ve([][]byte{[]byte("v1"), []byte("v2")}, nil),
|
ve([][]byte{[]byte("v1"), []byte("v2")}, nil),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"byteslices(nil)",
|
"float64s([v1, v2])",
|
||||||
ve(redis.ByteSlices(nil, nil)),
|
ve(redis.Float64s([]interface{}{[]byte("1.234"), []byte("5.678")}, nil)),
|
||||||
ve([][]byte(nil), redis.ErrNil),
|
ve([]float64{1.234, 5.678}, nil),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"values([v1, v2])",
|
"values([v1, v2])",
|
||||||
|
|||||||
28
vendor/github.com/garyburd/redigo/redis/scan.go
generated
vendored
28
vendor/github.com/garyburd/redigo/redis/scan.go
generated
vendored
@@ -110,6 +110,25 @@ func convertAssignInt(d reflect.Value, s int64) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func convertAssignValue(d reflect.Value, s interface{}) (err error) {
|
func convertAssignValue(d reflect.Value, s interface{}) (err error) {
|
||||||
|
if d.Kind() != reflect.Ptr {
|
||||||
|
if d.CanAddr() {
|
||||||
|
d2 := d.Addr()
|
||||||
|
if d2.CanInterface() {
|
||||||
|
if scanner, ok := d2.Interface().(Scanner); ok {
|
||||||
|
return scanner.RedisScan(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if d.CanInterface() {
|
||||||
|
// Already a reflect.Ptr
|
||||||
|
if d.IsNil() {
|
||||||
|
d.Set(reflect.New(d.Type().Elem()))
|
||||||
|
}
|
||||||
|
if scanner, ok := d.Interface().(Scanner); ok {
|
||||||
|
return scanner.RedisScan(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch s := s.(type) {
|
switch s := s.(type) {
|
||||||
case []byte:
|
case []byte:
|
||||||
err = convertAssignBulkString(d, s)
|
err = convertAssignBulkString(d, s)
|
||||||
@@ -135,11 +154,15 @@ func convertAssignArray(d reflect.Value, s []interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func convertAssign(d interface{}, s interface{}) (err error) {
|
func convertAssign(d interface{}, s interface{}) (err error) {
|
||||||
|
if scanner, ok := d.(Scanner); ok {
|
||||||
|
return scanner.RedisScan(s)
|
||||||
|
}
|
||||||
|
|
||||||
// Handle the most common destination types using type switches and
|
// Handle the most common destination types using type switches and
|
||||||
// fall back to reflection for all other types.
|
// fall back to reflection for all other types.
|
||||||
switch s := s.(type) {
|
switch s := s.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
// ingore
|
// ignore
|
||||||
case []byte:
|
case []byte:
|
||||||
switch d := d.(type) {
|
switch d := d.(type) {
|
||||||
case *string:
|
case *string:
|
||||||
@@ -219,6 +242,8 @@ func convertAssign(d interface{}, s interface{}) (err error) {
|
|||||||
|
|
||||||
// Scan copies from src to the values pointed at by dest.
|
// Scan copies from src to the values pointed at by dest.
|
||||||
//
|
//
|
||||||
|
// Scan uses RedisScan if available otherwise:
|
||||||
|
//
|
||||||
// The values pointed at by dest must be an integer, float, boolean, string,
|
// The values pointed at by dest must be an integer, float, boolean, string,
|
||||||
// []byte, interface{} or slices of these types. Scan uses the standard strconv
|
// []byte, interface{} or slices of these types. Scan uses the standard strconv
|
||||||
// package to convert bulk strings to numeric and boolean types.
|
// package to convert bulk strings to numeric and boolean types.
|
||||||
@@ -359,6 +384,7 @@ var errScanStructValue = errors.New("redigo.ScanStruct: value must be non-nil po
|
|||||||
//
|
//
|
||||||
// Fields with the tag redis:"-" are ignored.
|
// Fields with the tag redis:"-" are ignored.
|
||||||
//
|
//
|
||||||
|
// Each field uses RedisScan if available otherwise:
|
||||||
// Integer, float, boolean, string and []byte fields are supported. Scan uses the
|
// Integer, float, boolean, string and []byte fields are supported. Scan uses the
|
||||||
// standard strconv package to convert bulk string values to numeric and
|
// standard strconv package to convert bulk string values to numeric and
|
||||||
// boolean types.
|
// boolean types.
|
||||||
|
|||||||
58
vendor/github.com/garyburd/redigo/redis/scan_test.go
generated
vendored
58
vendor/github.com/garyburd/redigo/redis/scan_test.go
generated
vendored
@@ -19,10 +19,32 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/garyburd/redigo/redis"
|
"github.com/garyburd/redigo/redis"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type durationScan struct {
|
||||||
|
time.Duration `redis:"sd"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *durationScan) RedisScan(src interface{}) (err error) {
|
||||||
|
if t == nil {
|
||||||
|
return fmt.Errorf("nil pointer")
|
||||||
|
}
|
||||||
|
switch src := src.(type) {
|
||||||
|
case string:
|
||||||
|
t.Duration, err = time.ParseDuration(src)
|
||||||
|
case []byte:
|
||||||
|
t.Duration, err = time.ParseDuration(string(src))
|
||||||
|
case int64:
|
||||||
|
t.Duration = time.Duration(src)
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("cannot convert from %T to %T", src, t)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var scanConversionTests = []struct {
|
var scanConversionTests = []struct {
|
||||||
src interface{}
|
src interface{}
|
||||||
dest interface{}
|
dest interface{}
|
||||||
@@ -59,6 +81,11 @@ var scanConversionTests = []struct {
|
|||||||
{[]interface{}{[]byte("1"), []byte("2")}, []float64{1, 2}},
|
{[]interface{}{[]byte("1"), []byte("2")}, []float64{1, 2}},
|
||||||
{[]interface{}{[]byte("1")}, []byte{1}},
|
{[]interface{}{[]byte("1")}, []byte{1}},
|
||||||
{[]interface{}{[]byte("1")}, []bool{true}},
|
{[]interface{}{[]byte("1")}, []bool{true}},
|
||||||
|
{"1m", durationScan{Duration: time.Minute}},
|
||||||
|
{[]byte("1m"), durationScan{Duration: time.Minute}},
|
||||||
|
{time.Minute.Nanoseconds(), durationScan{Duration: time.Minute}},
|
||||||
|
{[]interface{}{[]byte("1m")}, []durationScan{durationScan{Duration: time.Minute}}},
|
||||||
|
{[]interface{}{[]byte("1m")}, []*durationScan{&durationScan{Duration: time.Minute}}},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScanConversion(t *testing.T) {
|
func TestScanConversion(t *testing.T) {
|
||||||
@@ -86,6 +113,8 @@ var scanConversionErrorTests = []struct {
|
|||||||
{int64(-1), byte(0)},
|
{int64(-1), byte(0)},
|
||||||
{[]byte("junk"), false},
|
{[]byte("junk"), false},
|
||||||
{redis.Error("blah"), false},
|
{redis.Error("blah"), false},
|
||||||
|
{redis.Error("blah"), durationScan{Duration: time.Minute}},
|
||||||
|
{"invalid", durationScan{Duration: time.Minute}},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestScanConversionError(t *testing.T) {
|
func TestScanConversionError(t *testing.T) {
|
||||||
@@ -158,6 +187,8 @@ type s1 struct {
|
|||||||
Bt bool
|
Bt bool
|
||||||
Bf bool
|
Bf bool
|
||||||
s0
|
s0
|
||||||
|
Sd durationScan `redis:"sd"`
|
||||||
|
Sdp *durationScan `redis:"sdp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var scanStructTests = []struct {
|
var scanStructTests = []struct {
|
||||||
@@ -166,8 +197,31 @@ var scanStructTests = []struct {
|
|||||||
value interface{}
|
value interface{}
|
||||||
}{
|
}{
|
||||||
{"basic",
|
{"basic",
|
||||||
[]string{"i", "-1234", "u", "5678", "s", "hello", "p", "world", "b", "t", "Bt", "1", "Bf", "0", "X", "123", "y", "456"},
|
[]string{
|
||||||
&s1{I: -1234, U: 5678, S: "hello", P: []byte("world"), B: true, Bt: true, Bf: false, s0: s0{X: 123, Y: 456}},
|
"i", "-1234",
|
||||||
|
"u", "5678",
|
||||||
|
"s", "hello",
|
||||||
|
"p", "world",
|
||||||
|
"b", "t",
|
||||||
|
"Bt", "1",
|
||||||
|
"Bf", "0",
|
||||||
|
"X", "123",
|
||||||
|
"y", "456",
|
||||||
|
"sd", "1m",
|
||||||
|
"sdp", "1m",
|
||||||
|
},
|
||||||
|
&s1{
|
||||||
|
I: -1234,
|
||||||
|
U: 5678,
|
||||||
|
S: "hello",
|
||||||
|
P: []byte("world"),
|
||||||
|
B: true,
|
||||||
|
Bt: true,
|
||||||
|
Bf: false,
|
||||||
|
s0: s0{X: 123, Y: 456},
|
||||||
|
Sd: durationScan{Duration: time.Minute},
|
||||||
|
Sdp: &durationScan{Duration: time.Minute},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
7
vendor/github.com/garyburd/redigo/redis/test_test.go
generated
vendored
7
vendor/github.com/garyburd/redigo/redis/test_test.go
generated
vendored
@@ -38,6 +38,7 @@ var (
|
|||||||
ErrNegativeInt = errNegativeInt
|
ErrNegativeInt = errNegativeInt
|
||||||
|
|
||||||
serverPath = flag.String("redis-server", "redis-server", "Path to redis server binary")
|
serverPath = flag.String("redis-server", "redis-server", "Path to redis server binary")
|
||||||
|
serverAddress = flag.String("redis-address", "127.0.0.1", "The address of the server")
|
||||||
serverBasePort = flag.Int("redis-port", 16379, "Beginning of port range for test servers")
|
serverBasePort = flag.Int("redis-port", 16379, "Beginning of port range for test servers")
|
||||||
serverLogName = flag.String("redis-log", "", "Write Redis server logs to `filename`")
|
serverLogName = flag.String("redis-log", "", "Write Redis server logs to `filename`")
|
||||||
serverLog = ioutil.Discard
|
serverLog = ioutil.Discard
|
||||||
@@ -96,7 +97,8 @@ func (s *Server) watch(r io.Reader, ready chan error) {
|
|||||||
text = scn.Text()
|
text = scn.Text()
|
||||||
fmt.Fprintf(serverLog, "%s\n", text)
|
fmt.Fprintf(serverLog, "%s\n", text)
|
||||||
if !listening {
|
if !listening {
|
||||||
if strings.Contains(text, "The server is now ready to accept connections on port") {
|
if strings.Contains(text, " * Ready to accept connections") ||
|
||||||
|
strings.Contains(text, " * The server is now ready to accept connections on port") {
|
||||||
listening = true
|
listening = true
|
||||||
ready <- nil
|
ready <- nil
|
||||||
}
|
}
|
||||||
@@ -135,6 +137,7 @@ func startDefaultServer() error {
|
|||||||
defaultServer, defaultServerErr = NewServer(
|
defaultServer, defaultServerErr = NewServer(
|
||||||
"default",
|
"default",
|
||||||
"--port", strconv.Itoa(*serverBasePort),
|
"--port", strconv.Itoa(*serverBasePort),
|
||||||
|
"--bind", *serverAddress,
|
||||||
"--save", "",
|
"--save", "",
|
||||||
"--appendonly", "no")
|
"--appendonly", "no")
|
||||||
return defaultServerErr
|
return defaultServerErr
|
||||||
@@ -146,7 +149,7 @@ func DialDefaultServer() (Conn, error) {
|
|||||||
if err := startDefaultServer(); err != nil {
|
if err := startDefaultServer(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
c, err := Dial("tcp", fmt.Sprintf(":%d", *serverBasePort), DialReadTimeout(1*time.Second), DialWriteTimeout(1*time.Second))
|
c, err := Dial("tcp", fmt.Sprintf("%v:%d", *serverAddress, *serverBasePort), DialReadTimeout(1*time.Second), DialWriteTimeout(1*time.Second))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
42
vendor/github.com/gin-gonic/autotls/.editorconfig
generated
vendored
Normal file
42
vendor/github.com/gin-gonic/autotls/.editorconfig
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# unifying the coding style for different editors and IDEs => editorconfig.org
|
||||||
|
|
||||||
|
; indicate this is the root of the project
|
||||||
|
root = true
|
||||||
|
|
||||||
|
###########################################################
|
||||||
|
; common
|
||||||
|
###########################################################
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
|
||||||
|
end_of_line = LF
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
###########################################################
|
||||||
|
; make
|
||||||
|
###########################################################
|
||||||
|
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
[makefile]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
###########################################################
|
||||||
|
; markdown
|
||||||
|
###########################################################
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
###########################################################
|
||||||
|
; golang
|
||||||
|
###########################################################
|
||||||
|
|
||||||
|
[*.go]
|
||||||
|
indent_style = tab
|
||||||
27
vendor/github.com/gin-gonic/autotls/.gitignore
generated
vendored
Normal file
27
vendor/github.com/gin-gonic/autotls/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
*.prof
|
||||||
|
|
||||||
|
vendor/*
|
||||||
|
!vendor/vendor.json
|
||||||
27
vendor/github.com/gin-gonic/autotls/.travis.yml
generated
vendored
Normal file
27
vendor/github.com/gin-gonic/autotls/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
language: go
|
||||||
|
sudo: false
|
||||||
|
go:
|
||||||
|
- 1.6.x
|
||||||
|
- 1.7.x
|
||||||
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
|
- master
|
||||||
|
|
||||||
|
git:
|
||||||
|
depth: 3
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get -v github.com/kardianos/govendor
|
||||||
|
- govendor sync
|
||||||
|
- go get -u github.com/campoy/embedmd
|
||||||
|
|
||||||
|
script:
|
||||||
|
- embedmd -d README.md
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
webhooks:
|
||||||
|
urls:
|
||||||
|
- https://webhooks.gitter.im/e/7f95bf605c4d356372f4
|
||||||
|
on_success: change # options: [always|never|change] default: always
|
||||||
|
on_failure: always # options: [always|never|change] default: always
|
||||||
|
on_start: false # default: false
|
||||||
21
vendor/github.com/gin-gonic/autotls/LICENSE
generated
vendored
Normal file
21
vendor/github.com/gin-gonic/autotls/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 Gin-Gonic
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
67
vendor/github.com/gin-gonic/autotls/README.md
generated
vendored
Normal file
67
vendor/github.com/gin-gonic/autotls/README.md
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# autotls
|
||||||
|
|
||||||
|
[](https://travis-ci.org/gin-gonic/autotls)
|
||||||
|
[](https://goreportcard.com/report/github.com/gin-gonic/autotls)
|
||||||
|
[](https://godoc.org/github.com/gin-gonic/autotls)
|
||||||
|
[](https://gitter.im/gin-gonic/autotls?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
|
Support Let's Encrypt for a Go server application.
|
||||||
|
|
||||||
|
## example
|
||||||
|
|
||||||
|
example for 1-line LetsEncrypt HTTPS servers.
|
||||||
|
|
||||||
|
[embedmd]:# (example/example1.go go)
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/autotls"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
|
||||||
|
// Ping handler
|
||||||
|
r.GET("/ping", func(c *gin.Context) {
|
||||||
|
c.String(200, "pong")
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Fatal(autotls.Run(r, "example1.com", "example2.com"))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
example for custom autocert manager.
|
||||||
|
|
||||||
|
[embedmd]:# (example/example2.go go)
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/autotls"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"golang.org/x/crypto/acme/autocert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
|
||||||
|
// Ping handler
|
||||||
|
r.GET("/ping", func(c *gin.Context) {
|
||||||
|
c.String(200, "pong")
|
||||||
|
})
|
||||||
|
|
||||||
|
m := autocert.Manager{
|
||||||
|
Prompt: autocert.AcceptTOS,
|
||||||
|
HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"),
|
||||||
|
Cache: autocert.DirCache("/var/www/.cache"),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Fatal(autotls.RunWithManager(r, &m))
|
||||||
|
}
|
||||||
|
```
|
||||||
24
vendor/github.com/gin-gonic/autotls/autotls.go
generated
vendored
Normal file
24
vendor/github.com/gin-gonic/autotls/autotls.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package autotls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/acme/autocert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Run support 1-line LetsEncrypt HTTPS servers
|
||||||
|
func Run(r http.Handler, domain ...string) error {
|
||||||
|
return http.Serve(autocert.NewListener(domain...), r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunWithManager support custom autocert manager
|
||||||
|
func RunWithManager(r http.Handler, m *autocert.Manager) error {
|
||||||
|
s := &http.Server{
|
||||||
|
Addr: ":https",
|
||||||
|
TLSConfig: &tls.Config{GetCertificate: m.GetCertificate},
|
||||||
|
Handler: r,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.ListenAndServeTLS("", "")
|
||||||
|
}
|
||||||
23
vendor/github.com/gin-gonic/autotls/doc.go
generated
vendored
Normal file
23
vendor/github.com/gin-gonic/autotls/doc.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// Package autotls support Let's Encrypt for a Go server application.
|
||||||
|
//
|
||||||
|
// package main
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// "log"
|
||||||
|
//
|
||||||
|
// "github.com/gin-gonic/autotls"
|
||||||
|
// "github.com/gin-gonic/gin"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
|
// r := gin.Default()
|
||||||
|
//
|
||||||
|
// // Ping handler
|
||||||
|
// r.GET("/ping", func(c *gin.Context) {
|
||||||
|
// c.String(200, "pong")
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// log.Fatal(autotls.Run(r, "example1.com", "example2.com"))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
package autotls
|
||||||
19
vendor/github.com/gin-gonic/autotls/example/example1.go
generated
vendored
Normal file
19
vendor/github.com/gin-gonic/autotls/example/example1.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/autotls"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
|
||||||
|
// Ping handler
|
||||||
|
r.GET("/ping", func(c *gin.Context) {
|
||||||
|
c.String(200, "pong")
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Fatal(autotls.Run(r, "example1.com", "example2.com"))
|
||||||
|
}
|
||||||
26
vendor/github.com/gin-gonic/autotls/example/example2.go
generated
vendored
Normal file
26
vendor/github.com/gin-gonic/autotls/example/example2.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/autotls"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"golang.org/x/crypto/acme/autocert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
|
||||||
|
// Ping handler
|
||||||
|
r.GET("/ping", func(c *gin.Context) {
|
||||||
|
c.String(200, "pong")
|
||||||
|
})
|
||||||
|
|
||||||
|
m := autocert.Manager{
|
||||||
|
Prompt: autocert.AcceptTOS,
|
||||||
|
HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"),
|
||||||
|
Cache: autocert.DirCache("/var/www/.cache"),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Fatal(autotls.RunWithManager(r, &m))
|
||||||
|
}
|
||||||
19
vendor/github.com/gin-gonic/autotls/vendor/vendor.json
generated
vendored
Normal file
19
vendor/github.com/gin-gonic/autotls/vendor/vendor.json
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"comment": "",
|
||||||
|
"ignore": "test",
|
||||||
|
"package": [
|
||||||
|
{
|
||||||
|
"checksumSHA1": "QNHLX/9+KNzu0oCCdqUSEX4kyiY=",
|
||||||
|
"path": "golang.org/x/crypto/acme",
|
||||||
|
"revision": "cbc3d0884eac986df6e78a039b8792e869bff863",
|
||||||
|
"revisionTime": "2017-04-09T18:29:52Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "TrKJW+flz7JulXU3sqnBJjGzgQc=",
|
||||||
|
"path": "golang.org/x/crypto/acme/autocert",
|
||||||
|
"revision": "5ef0053f77724838734b6945dd364d3847e5de1d",
|
||||||
|
"revisionTime": "2017-06-29T04:06:47Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rootPath": "github.com/gin-gonic/autotls"
|
||||||
|
}
|
||||||
1
vendor/github.com/gin-gonic/contrib/README.md
generated
vendored
1
vendor/github.com/gin-gonic/contrib/README.md
generated
vendored
@@ -34,3 +34,4 @@ Each author is responsible of maintaining his own code, although if you submit a
|
|||||||
+ [gin-oauth2](https://github.com/zalando/gin-oauth2) - for working with OAuth2
|
+ [gin-oauth2](https://github.com/zalando/gin-oauth2) - for working with OAuth2
|
||||||
+ [static](https://github.com/hyperboloide/static) An alternative static assets handler for the gin framework.
|
+ [static](https://github.com/hyperboloide/static) An alternative static assets handler for the gin framework.
|
||||||
+ [xss-mw](https://github.com/dvwright/xss-mw) - XssMw is a middleware designed to "auto remove XSS" from user submitted input
|
+ [xss-mw](https://github.com/dvwright/xss-mw) - XssMw is a middleware designed to "auto remove XSS" from user submitted input
|
||||||
|
+ [gin-helmet](https://github.com/danielkov/gin-helmet) - Collection of simple security middleware.
|
||||||
|
|||||||
2
vendor/github.com/gin-gonic/contrib/ginrus/example/example.go
generated
vendored
2
vendor/github.com/gin-gonic/contrib/ginrus/example/example.go
generated
vendored
@@ -5,9 +5,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/gin-gonic/contrib/ginrus"
|
"github.com/gin-gonic/contrib/ginrus"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
2
vendor/github.com/gin-gonic/contrib/ginrus/ginrus.go
generated
vendored
2
vendor/github.com/gin-gonic/contrib/ginrus/ginrus.go
generated
vendored
@@ -6,8 +6,8 @@ package ginrus
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ginrus returns a gin.HandlerFunc (middleware) that logs requests using logrus.
|
// Ginrus returns a gin.HandlerFunc (middleware) that logs requests using logrus.
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user