Python 生成公众号头图 1.0

person holding sticky note

我的公众号头图属于特别简洁的,原因是我在乎的是文章中的内容,而不是头图。虽然我知道一个好的头图会提升点击率,但其背后耗费的时间是我目前不愿去做的。因此,我目前的头图都是 PS 打开一张图,然后直接放上一行文字来生成的。

历史头图
历史头图

但是,打开 PS 还是太花费精力了,因此,我希望可以用更少的时间来生成简单明了的头图。因此,我决定优化我的头图制作方案,使用代码来完成公众号头图的生成。

环境依赖

这一次的代码使用 Python 来完成,并基于 Python 的 Pillow 库完成图片的生成,因此,你在运行我的代码时,需要先安装 Pillow

pip install pillow

实现思路

在使用 Pillow 绘图的时候,和我们在前端使用 Canvas 绘图的思路基本一致,我们要做的是

  1. 准备特定背景的画布
  2. 加载字体
  3. 绘制文字
  4. 导出图片

生成的难度并不高,也只需要花费一些时间来编写代码。具体的代码我贴在下方了,供你参考。

代码实现

具体的代码如下

from PIL import Image, ImageDraw, ImageFont
# 常用变量
img_size = (900, 383)
bg_color= "#fca652"
big_text_size = (900,383)
text = "代码生成图片"
text_color = "#eeeeee"
filename = "export.jpg"
font_size = 80
# 生成画布
export_image = Image.new("RGB", img_size, bg_color)
# 加载字体
font = ImageFont.truetype("NotoSansSC-Medium.otf", font_size)
# 计算字体宽度
text_width = font.getsize(text)
text_coordinate = int((big_text_size[0]-text_width[0])/2), int((big_text_size[1]-text_width[1])/2-20)
# 生成绘图 ctx
img_draw = ImageDraw.Draw(export_image)
# 绘制文字
img_draw.text(text_coordinate, text, font=font, fill=text_color)
export_image.save(filename, quality=95)

总结

使用 Python 来绘制头图的难度并不大,比如今天的头图,就是通过代码来绘制的,你可以看看效果。

不过,上面的代码还是很简陋,你觉得这段代码有没有什么值得优化的点呢?

键盘设置如何优化小程序使用体验?

6ee6df690137fd06bc6166adb63caca1

在小程序开发过程中,用户输入是必不可少的,我们经常会需要用户输入一些内容,来完成产品收集用户信息的需求。

在这种情况下,我们可以考虑借助小程序提供的一些和键盘相关的 API 来优化小程序的使用体验。

Input 组件的 type 属性

Input 组件的 type 属性
Input 组件的 type 属性

从小程序的 1.0 版本开始,就支持为 input 组件设置 type,不同的 type 会显示不同的手机键盘。默认情况下,显示的是 text 文本输入键盘,这个键盘的特点是显示所有的内容,可以适用于所有的场景。

但,适用于所有场景也就意味着不适用于所有场景,总会在每一个场景中有着种种不便,因此,在实际的开发中,为了获得更佳的体验,你可以通过设置不同的 Type 来控制实际的键盘显示情况。

text 类型 input 适用建议
text 类型 input 适用建议

除了默认的 text 类以外,你还可以使用 number(数字输入键盘)、idcard 身份证输入键盘和 digit带小数点的数字键盘。

idcard 类型 input 适用建议
idcard 类型 input 适用建议

你可以根据自己的实际使用场景来设置不同的类型,比如说

  • 如果你的小程序的验证码都是数字的,那么你给出一个 text 类型的键盘,显然不如给一个 number 类型的键盘更合适。
  • 如果你的小程序中涉及到了手机号的输入,那么这种情况下你就可以选择使用 number 类型的键盘,来优化用户输入时的体验。
number 类型 input 适用建议
number 类型 input 适用建议

这里的思路是类似的,当你预期用户输入的内容只有数字,就可以考虑 numberdigitidcard等类型,来优化你的小程序的实际使用体验。

digit 类型 input 适用建议
digit 类型 input 适用建议

总结

input 组件默认提供的 四种 type ,可以通过选择不同的类型,从而获得不同的体验效果,从而对于你的小程序体验进行优化和推进。

我是如何优化博客的

由于主域名的备案掉了,所以我不得已,将站点从国内的 VPS 上迁移到了国外的 VPS 上,但是,站点迁移,速度不能下降。

事实上,经过一段优化,目前这个站点的速度着实不错,对我自己来说,基本上是秒开,而在测试网站上,速度也是杠杠的。

除了极个别监测点的速度实在是太慢,绝大多数的监测点都在 1s 内打开。

3elis
https://www.boce.com/http/www.ixiqin.com

能达到这样的速度,背后意味着很多种优化。我们一一来看。

原理

如果要对网站速度进行优化,就需要理解,在我们从浏览器中输入一个网址,到我们最终网页加载完成,都经历了哪些流程。

  1. 域名解析:我们输入在浏览器中的域名,是无法直接抵达目标主机的。因此,我们需要访问 DNS 服务,将域名转换为 IP 地址,从而才能抵达目标主机。
  2. 内容下载:当我们的请求抵达目标主机后,浏览器会下载服务器的返回。
  3. 本地渲染:浏览器将服务器的返回下载到本地后,会在浏览器的引擎中进行渲染。

当然, 在实际执行过程中,我们需要考虑的不仅仅是这三个点,不过就这三个点而言,也足够我们进行优化了,更加细致的,可以在后续遇见新的性能瓶颈后,进行优化。

分析

在理解了原理后,我们可以一一来分析

域名解析

对细节进行分析以后,可以看到,我的站点在解析时间上的耗时是普遍比较长的,大多是在 0.3 秒,这是一个优化点。

dzvhf

DNS 的优化总的来说,乏善可陈。因为系统底层对于 DNS 的机制只有两处,分别是

  1. Hosts
  2. DNS 解析

如果想要优化,那你就需要降低你的 DNS 解析时间。Hosts 显然是不靠谱的,你不可能让你的用户都去修改了 Hosts 后再使用。

而 DNS 解析,目前我使用的是免费的阿里云 DNS ,这里的优化其实依赖的是阿里云的 DNS 优化(付费版可能会好一点,不过我暂时不考虑上付费 DNS)。

内容下载

内容下载可以分为两个部分

  1. 内容生成:服务器计算得出结果。
  2. 内容下载:浏览器将服务器计算的结果下载到本地。

内容生成

在内容下载整个流程中,WordPress 主要的问题往往是卡在内容生成方面。

由于 WordPress 的功能强大,且在优化方面做的一般,因此,当我们的 WordPress 的内容足够多的时候, 数据库的查询就会开始变慢。

在这个时候,我们可以选择使用一些工具来加速站点的优化。一般的来说,会考虑使用 Key Value 数据库来替代直接使用 MySQL 进行查询。

这方面可选的包括

  • Memcached
  • Redis
  • 文件缓存

我因为懒得配置 MemeCached 和 Redis ,因此使用的是文件缓存,这里我用的是 WordPress 的 WP-Optimize 插件

kq8ap

可以看到,我的站点的缓存数量达到了 2716,缓存的大小达到了 45.6MB。也正是这些缓存,让我的站点在内容计算方面不需要耗费太多的时间,优化站点的体验。

当然,如果你追求更好的速度,可以考虑上 Redis 或 Memcache ,这些内容互联网上有非常多的教程,我就不再一一介绍。

内容下载

说完了内容生成,来谈一谈内容下载。

在内容下载方面,其实主要受限于你所使用的虚拟主机、VPS、云服务器性能。

在我们购买虚拟主机、云服务器的时候,我们往往会要购买相应的带宽。你购买的带宽越大,你的站点在下载时能够使用的带宽就会越大,相应的,下载的速度也就会越大。

在这方面,如果你的站点有备案,可以考虑购买一些国内的 CDN 服务,使用 CDN 来完成站点的加速,让你的站点加载速度可以变得更快一些。我的域名没有备案,所以没办法使用国内的 CDN,比较遗憾。

本地渲染

内容下载到本地,就会需要进行内容的渲染,而渲染的过程则和你的站点设计、标签数量、复杂度等都有关系。

你会发现,我现在站点使用的大多数是 WordPress 官方的主题,官方的主题的好处在于其站点设计简洁大方,同时页面的标签结构会比较合理,可以确保不会卡在一些奇奇怪怪得地方。

类似的,这也是为什么我很讨厌一些站点生成器(可视化拖拽工具),他们的确可以很方便的生成,但相应的,也会带来大量的冗余标签,让我觉得十分不优雅(当然,背后的人效计算又是另外一回事了)。

总结

对于你自己的博客来说,你也可以参照我的优化方法,来找到自己的问题点,并进行相应的优化,当然,如果你有什么想要讨论的,也欢迎你在下方的留言区和我一起分享。


Append:

我将 DNS 从阿里云免费 DNS 切换到 DNSPod 的个人专业版的以后,我的 DNS 解析时间获得大幅度下降,解析速度降为 0.13s 以内。如果你的解析速度也很高,值得试试。

如何免费为你的组织项目配置 Vercel

christopher gower m HRfLhgABo unsplash scaled 1

前言

Vercel 是我自己非常喜欢的平台,我自己的一些项目都会选择使用 Vercel 来部署。比如 LogolyEasyWPBook 等等。

主要是其在中国大陆的访问速度是比 Netlify 和 Github Pages 更快。但不得不说的是,Vercel 提供了 Production 环境和 Development 环境,对于项目开发时是非常有用的,当你提交一个 commit 或者是一个 PR 时, Vercel 会自动帮你部署,并将相应的 URL comment 到你的 commit 或 PR 下,你只需要点击相应链接,就可以预览效果,十分方便。

但 Vercel 的 Teams 功能是付费的,对于一些 Organization 来说,你需要付费用 Vercel 成本是比较高的。特别是如果你是一个 OpenSource Project,你大概率入不敷出。在这种情况下,如何才能在 Organization Project 中免费使用 Vercel 呢?我考虑了一个绕过去的方法。

我们之所以无法使用在项目中无法免费使用 Vercel 是 Vercel 的产品策略问题。

kpub9

而我们被禁用的无法使用官方自建的 Github Apps 来部署,但不意味着我们不可以自己实现这样的逻辑,来实现在组织内项目进行push。

而想要达成这样的效果,需要你具备两个条件

  1. 你的代码每一次提交都会自动触发 CI
  2. 你的 CI 中可以自动上传代码到 Vercel

而前者,可以通过 Github 提供的 Action 来完成。后者则可以通过编写代码来完成。不过好在,我在 Github 的 Marketplace 中找到了别人已经写好的 Action ,因此又可以少写一些代码(开心?)。

接下来,看看具体要如何操作。

步骤

安装依赖

在进行后续的操作之前, 你需要先安装配置过程中的依赖。

执行如下命令安装 Vercel CLI

npm i -g vercel

创建一个项目

为了正常部署,你需要创建一个项目,并将其 push 到 Github。当然,如果你已经创建好了项目,就直接把项目 Clone 到本地就好。

使用 Vercel 部署这个项目

这一步是为了在 Vercel 上创建一个项目,因为 Vercel 不支持通过网页端创建项目,只支持在 CLI 或通过 Git 接入自动创建。

在你的项目根目录,执行命令 vercel 来触发 Vercel 的部署,它会自动上传你的项目到 Vercel 。如果你之前登录过,会看到类似这样的界面,根据其提示进行配置就好。如果你之前没有登录过,你登录后会出现类似的界面。根据提示进行配置。

rwh9j

获取配置

想要部署一个项目到 Vercel ,你需要知道三样东西

  1. Access Token: 用于 CLI 的登录和 API 的调用,没有 Access Token ,Vercel 就没有办法获取到你的用户身份,进行鉴权校验。
  2. Org ID:Org ID 是指你在 Vercel 上的用户 ID。由于 Vercel 中一个人其实可以管理和配置多个 Team ,因此需要借助 Org ID 来指定要管理哪个账号下的项目。
  3. Project ID:很好理解,如果你要管理 Project ,就需要一个 Project ID。

其中,Access Token 你需要从 Vercel 后台的 Tokens 页面获取。

sohur

而 Org ID 则可以从你的项目中的 .vercel/project.json 中获取。你会获得类似下面这样的内容,其中的 OrgID 就是你在 Vercel 上的 ID ,而 Project ID 就是对应的项目 ID。

{"orgId":"r359XAnYONVAmiXtdxZ22A2E","projectId":"Qma3GdwoiAfJSsbsSydBgaCDh8LJj6wTWvvqpUwrN6J2F3"}
Code language: JSON / JSON with Comments (json)

准备好了这三项以后,就可以进入到下一个环节,配置 Action Secret 了。

配置 Github Action Secret

Github Action 提供了 Secret 的机制,用来让你保存哪些安全密钥,避免直接写在 Action File 中,出现泄漏的可能。

打开你的项目的 Settings – Secrets 页面

oi5yn

在这里你可以新增 Secret ,你需要新增三个 Secret ,分别是

  • ORG_ID: 填写你从 .vercel/project.json 中获取到的 orgId;
  • PROJECT_ID:填写你从 .vercel/project.json 中获取到的 projectId;
  • VERCEL_TOKEN:填写你从 Vercel 后台生成的 Token。

这样,就配置好了 Secret ,接下来就可以在你的项目中加入 a、Action 配置文件,完成项目的配置。

添加 Action 配置文件

根据 Github 的规范,你需要将你的 Action 配置文件放置在项目根目录的 .github/workflows/xxx.yml 文件中,其中 xxx 是你的 Action 名,比如叫 vercel.yml

这里我使用的是 ngduc 写的 vercel-deploy-action

按照路径创建好 vercel.yml 文件,然后在其中加入如下代码

name: deploy website
on:
  push:
    branches:
      - master
  pull_request:
    branches:
      - master
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: ngduc/vercel-deploy-action@master
        with:
          vercel-cli: vercel
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.ORG_ID}}
          vercel-project-id: ${{ secrets.PROJECT_ID}}
          github-token: ${{ secrets.GITHUB_TOKEN }}

粘贴进去以后,将 vercel.yml 添加到 git 控制中,并提交到 Github 上,就可以实现触发 Vercel 了。

总结

虽然 Vercel 本身不能免费提供 Organization 的支持,但是我们可以通过一些小的技巧,绕过其官方的配置。对于一些只是将 Vercel 配置为开发者预览的场景下来说,还是足够的。

但如果你希望将 Vercel 配置为生产环境,那么 Teams Plan 可以提供的更多的权限控制,会是你需要的,买一个,也不贵,是吧?

ocfv6

hexo-generator-podcasts 一个强大的 Hexo 播客生成插件

eultm

hexo-generator-podcasts 是我近期开发的一款 Hexo 的播客生成器插件,hexo-generatro-podcast 可以帮助你在你的 Hexo 博客中加入播客的功能,你可以通过简单的配置,在自己的博客中加入播客的配置,轻松上架播客。

不仅如此,如果你需要部署多个播客,也可以直接借助这个插件,在一个站点中部署多个播客,特别适用于一些大型的播客组织,可以在一个站点同事部署多个播客。

如果你感兴趣,不妨去试试看: https://github.com/bestony/hexo-generator-podcasts/

此外,我自己的播客生产力维基,就是使用这个插件构建的。

进行 Hexo 开发时,如何去除缓存?

表现

最近我在开发一个 Hexo 插件,在开发的过程中,遇到了一些小的问题:插件的代码发生了修改,但运行hexo 的时候却没有实时刷新出来

原因

在对代码进行分析后发现,hexo 的 tag filter 的结果会缓存在数据库中,由于缓存了,所以我使用的 generator ,所以也会被缓存。

解决方案

在预览前,删除 db.json 即可让 hexo 重新开始生成

为了方便,我在 npm 的 scripts 中加入了一个自定义的配置

{
 ...
 "scripts":{
  ...
  "s": "rm db.json && hexo server"
  ...
 }
 ...
}
Code language: JavaScript (javascript)

Golang 中如何为 XML 加入 CDATA 支持

shallow focus photography of computer codes

需求

最近在参与 WavPub 的开发,在开发的过程中,需要调整 XML 的结构,因此,需要为一些字段加入 CDATA 的支持。

问题

在阅读了 eduncan911/podcast 中的代码后发现,这个包在生成 XML 的时候,使用的是 Golang 核心库中的 encoding/xml 包,而这个包在使用的时候有一个问题,你可以给其字段加入 ,cdata 来完成加入 cdata 的标签,但问题在于,他的实现是,在你的字段外部加标签,而不是内部加标签。举个例子来说,就是,如果你定义了字段为 xml:”category,cdata” 你得到的会是

<![CDATA[ somecode ]]>
Code language: HTML, XML (xml)

而非我们想要的

<category> <![CDATA[ xxx ]]> </category>
Code language: HTML, XML (xml)

实现

想要解决这个问题,就需要你在你的字段中实现一层包裹,在其自动生成的 CDATA 外层加入一层 XML ,这样就可以实现我们想要的效果,比如说我上面的效果可以通过定义一个新的 Description 的 Struct 来实现

package podcast
import "encoding/xml"
// Description represents text inputs.
type Description struct {
	XMLName xml.Name `xml:"description"`
	Text    string   `xml:",cdata"`
}
Code language: JavaScript (javascript)

然后,再在需要的地方,加入相应的引用就好

type Podcast struct {
	XMLName        xml.Name `xml:"channel"`
	...
	Description    *Description
        ...
}
Code language: JavaScript (javascript)

Reference

https://pkg.go.dev/encoding/xml?tab=doc

https://play.golang.org/p/xRn6fe0ilj

Golang 返回随机值

需求

在某些场景下,需要根据给定值,返回一批特定的结果,在这种情况下,需要返回切片中的某一个特定的值。

实现

package main
import (
	"fmt"
	"math/rand"
	"time"
)
func main() {
	userAgentSlice := []string{
		"Podcasts/1430.46+CFNetwork/1125.2+Darwin/19.4.0",
		"Spotify/1.0",
		"PocketCasts/1.0+(Pocket+Casts+Feed+Parser;++http://pocketcasts.com/)",
		"iTMS",
		"AirPodcasts/1440.4+CFNetwork/1126+Darwin/19.5.0",
		"Tentacles,+Like+iTunes",
	}
	rand.Seed(time.Now().Unix())
	fmt.Println(userAgentSlice[rand.Intn(len(userAgentSlice))])
}
Code language: JavaScript (javascript)

写了个简单的接力工具

两会期间,各种工具的效果都会大幅度下降。在这种情况下,作为翻译组,想要好好干活也是挺麻烦的,于是,便写了一些简单的工具,来解决这种特殊情况下的尴尬问题。

场景描述

在翻译组,我们有专门的机器来负责内容抓取 & 转化成为 markdown,但同时,文章中会有一些图片,种种原因,会导致这些图片无法访问。在翻译的时候,我们需要对这些图片进行下载,在这种情况下,就会很麻烦。我们需要一个更加简单的方式,来完成文件的下载。

解决方案 – Simple Proxy

Simple Proxy 就是来解决这个问题的,他可以让你很方便的完成外网的文件下载(当然,前提是你的 Proxy Server 可以访问到这个文件。

使用方法:

  1. 找到你要下载的文件,比如 https://img.linux.net.cn/data/attachment/album/202005/23/201448jcxlcci1f1z4c2l2.jpg
  2. 在你的域名前加入测试域名作为前缀,比如 https://simpleproxydemo.herokuapp.com/img.linux.net.cn/data/attachment/album/202005/23/201448jcxlcci1f1z4c2l2.jpg
  3. 访问新的地址,就会自动提醒你下载文件了。

使用建议

在实际的使用过程中,我给你的建议是,

  1. 使用 Heroku 部署 + 使用 Cloudflare 加速 + 一个短域名

这样可以让你的使用体验达到最佳。

如何自己部署

参考:https://github.com/bestony/simple-proxy

原理

Simple Proxy 的代码十分简单,加入了大量的注释,也不过 51 行。

最为核心的代码源自于其中的

res.set({
  'Content-Disposition': `attachment; filename=${filename}`
})
Code language: JavaScript (javascript)

这段代码的用户是为返回值设定 Header ,其中用到的 Header Content-Disposition 是 HTTP 协议早期定义的 Header 规范。

如果你将其值设置为 inline,其内容将会直接展示在界面中;

其值设置为 attachment 则可以启动系统浏览器自带的下载功能。

其值中加入 filename 则可以更进一步,在下载时,指定下载的名称,在本次的项目中,就借助了这样的功能,让下载的文件名不发生改变。

Reference

关于 Podcast Feed 的一些 SPEC

Podcast 依赖于 RSS Feed ,因此,可以参考一些官方的页面了解 Podcast Feed 的格式。

一行代码把 [[xxx]] 替换为 [xxx](xxx)格式

[[xxx]] 是目前比较常见的 backlink ,特别是各种笔记应用。

在转化格式的时候,如果你需要将 [[xxx]] 替换为传统的 markdown 格式,这个时候你可以借助一个简单的 sed 命令来完成这些工作

替换代码

使用时把 test.md 替换为源文件,output.md 替换为导出的文件

sed -E "s/\[{2}(.*)\]{2}/[\\1](\\1)/g" test.md >> output.md
Code language: JavaScript (javascript)

这段代码使用了 sed 来完成修改,其中使用正则表达式替换 backlink 。

说起正则表达式,在我高中的时候,我误以为正则表达式是一种数学表达式,还跑去问了问我的高中数学老师,老师理所当然的不知道。

一些好用的工具

我自己在测试正则表达式的时候,会用到的工具主要是 RegExr ,不过最近发现有人部署了一个速度更快的 RegExr-CN,有需要的同学可以试试看

为什么没有用 -i Flag?

因为 -i 的话,怕把文件搞坏而没有备份,用管道虽然麻烦了点,但至少不修改源文件,安全一些。


change log

2020.05.24 fix some layout error

Rust + MicroBit 按钮调试代码

#![no_main]
#![no_std]
use panic_halt;
use microbit::hal::nrf51::{interrupt, GPIOTE, UART0};
use microbit::hal::prelude::*;
use microbit::hal::serial;
use microbit::hal::serial::BAUD115200;
use cortex_m::interrupt::Mutex;
use cortex_m::peripheral::Peripherals;
use cortex_m_rt::entry;
use core::cell::RefCell;
use core::fmt::Write;
use core::ops::DerefMut;
static GPIO: Mutex<RefCell<Option<GPIOTE>>> = Mutex::new(RefCell::new(None));
static TX: Mutex<RefCell<Option<serial::Tx<UART0>>>> = Mutex::new(RefCell::new(None));
#[entry]
fn main() -> ! {
    if let (Some(p), Some(mut cp)) = (microbit::Peripherals::take(), Peripherals::take()) {
        // 引入两个版本的外设
        cortex_m::interrupt::free(move |cs| {
            /* 开启外部设备的 GPIO 中断 */
            cp.NVIC.enable(microbit::Interrupt::GPIOTE);
            microbit::NVIC::unpend(microbit::Interrupt::GPIOTE);
            /* 切分 GPIO 口,方便使用 */
            let gpio = p.GPIO.split();
            /* 将 Button 的 IO 口作为输入 IO 口 */
            let _ = gpio.pin26.into_floating_input();
            let _ = gpio.pin17.into_floating_input();
            /* 当 GPIO 17 ( A 键)出现了下降沿的时候,触发中断 */
            p.GPIOTE.config[0]
                .write(|w| unsafe { w.mode().event().psel().bits(17).polarity().hi_to_lo() });
            p.GPIOTE.intenset.write(|w| w.in0().set_bit());
            p.GPIOTE.events_in[0].write(|w| unsafe { w.bits(0) });
            /* 当 GPIO 26 (B键)出现了下降沿的时候,触发中断 */
            p.GPIOTE.config[1]
                .write(|w| unsafe { w.mode().event().psel().bits(26).polarity().hi_to_lo() });
            p.GPIOTE.intenset.write(|w| w.in1().set_bit());
            p.GPIOTE.events_in[1].write(|w| unsafe { w.bits(0) });
            *GPIO.borrow(cs).borrow_mut() = Some(p.GPIOTE);
            /* 根据需要,设置 GPIO 口作为输入输出口 */
            let tx = gpio.pin24.into_push_pull_output().downgrade();
            let rx = gpio.pin25.into_floating_input().downgrade();
            /* 将准备的 GPIO 口作为串口来使用 */
            let (mut tx, _) = serial::Serial::uart0(p.UART0, tx, rx, BAUD115200).split();
            let _ = write!(
                tx,
                "\n\rWelcome to the buttons demo. Press buttons A and/or B for some action.\n\r",
            );
            *TX.borrow(cs).borrow_mut() = Some(tx);
        });
    }
    loop {
        continue;
    }
}
// 定义一个中断(如果函数出现了错误,就会触发中断),当我从按钮按下接收到了一个中断,这个函数就会被调用。
#[interrupt]
fn GPIOTE() {
    /* 进入中断内部的内容 */
    cortex_m::interrupt::free(|cs| {
        if let (Some(gpiote), &mut Some(ref mut tx)) = (
            GPIO.borrow(cs).borrow().as_ref(),
            TX.borrow(cs).borrow_mut().deref_mut(),
        ) {
            let buttonapressed = gpiote.events_in[0].read().bits() != 0; // 识别出 A 键按下
            let buttonbpressed = gpiote.events_in[1].read().bits() != 0; // 识别出 B 键按下
            /* 将按钮打印到串口中 */
            let _ = write!(
                tx,
                "Button pressed {}\n\r",
                match (buttonapressed, buttonbpressed) {
                    (false, false) => "",
                    (true, false) => "A",
                    (false, true) => "B",
                    (true, true) => "A + B",
                }
            );
            /* 清空事件 */
            gpiote.events_in[0].write(|w| unsafe { w.bits(0) });
            gpiote.events_in[1].write(|w| unsafe { w.bits(0) });
        }
    });
}

Code language: PHP (php)

如何 Docker 化一个 Cli 工具

需求

我在看 Hexo 的 issue 时,看到了一个需求

Docker image to avoid the environment setting issue.

刚好,我自己有 Docker 的基础,就决定提交一个 PR ,解决这个问题。

核心实现

在开发这一部分的时候,一个最核心的问题是,你需要准备 2 个文件,一个是 DockerFile ,另一个是对应的 Bash Script。

原因在于

  • Docker File 用于打包基础环境,比如全局安装 Hexo
  • Bash Script 则是为了方便挂载本地的文件系统,开辟端口等(端口可以放在 Docker file 中,文件系统必须要现场挂载,因为你的目的是使用 Cli 管理本地文件,就一定要把文件挂载过去)

具体实现的思路是,Docker 镜像本身提供的是基础环境,将 CMD 设置为 Bash ,方便执行具体的命令。

而 Bash Script 则将需要执行的命令整体传递过去。

代码

Docker File

FROM node:10
RUN npm install -g hexo-cli
CMD ["/bin/bash"]
Code language: CSS (css)

Bash Script

#!/bin/sh
docker run \
  --interactive --tty --rm \
  --volume "$PWD":/hexosite \
  --workdir /hexosite \
  -p 4000:4000 \
  bestony/hexojs:latest "$@"

Code language: JavaScript (javascript)

总结

Docker 化 Cli 命令其实并不复杂,核心在于 CMD 与你的 Bash Script 的配合。

其他

你可以查看 https://github.com/hexojs/hexo/pull/3891 来学习到更多的内容。