Skip to main content

· 2 min read
wen

前言

Docusaurus 是一个由 Facebook 开发的开源项目,用于构建静态网站。

它是一个基于 React 的静态网站生成器,可以帮助您快速创建网站。

Docusaurus 还提供了一个易于使用的博客插件,可以帮助您创建博客。

Docusaurus 默认不提供 RSS 导航菜单,在本文中,我们将介绍如何为 Docusaurus 博客添加 RSS 导航菜单。

怎么给 Docusaurus 博客 添加 RSS 导航菜单

docusaurus.config.js
module.exports = {
...
themeConfig: {
...
navbar: {
items: [
// blog rss
{
href: '/blog/rss.xml',
label: 'RSS',
position: 'right',
target: '_blank', // Open the link in a new tab/window
},
],
...

补充说明

BTW, 如果用 to 选项的话,点击 RSS 链接会报页面无法找到的 404 错误,

因为 Docusaurus 会把 to 选项的值当作一个页面的路径,而不是一个链接。

docusaurus.config.js
module.exports = {
...
themeConfig: {
...
navbar: {
items: [
// blog rss
{
to: '/blog/rss.xml',
label: 'RSS',
position: 'right',
},
],
...

· 2 min read
wen

问题

有 2 个数组,互相可能有重复的元素,如何合并这两个数组并去重?

比如有两个数组:

type User struct {
ID int // ID 作为唯一标识 (ID相同则认为是同一个元素)
Name string
}

old := []User{
{ID: 1, Name: "a"}, // only in old
{ID: 2, Name: "b"}, // 重复
}

new := []User{
{ID: 2, Name: "c"}, // 重复
{ID: 3, Name: "d"}, // only in new
}

合并后的结果应该是:

c := []User{
{ID: 1, Name: "a"}, // only in old
{ID: 2, Name: "c"}, // 重复 (保留 new 中的)
{ID: 3, Name: "d"}, // only in new
}

解决方案(一般化)

package main

import "fmt"

type User struct {
ID int
Name string
}

// contains Check if an element exists in a slice.
// keyFunc is used to uniquely identify the elements.
func contains(slice []any, item any, keyFunc func(any) any) bool {
for _, element := range slice {
if keyFunc(element) == keyFunc(item) {
return true
}
}
return false
}

// mergeSlices Merges two slices and removes duplicates.
//
// keyFunc is used to uniquely identify the elements.
// if an element exists in both old and new, the element in new takes precedence.
// old and new are assumed to have no duplicate elements.
// The order is not guaranteed.
func MergeSlices(old, new []any, keyFunc func(any) any) []any {
var merged []any

// copy new to merged
merged = append(merged, new...)

for _, item := range old {
if !contains(merged, item, keyFunc) {
merged = append(merged, item)
}
}

return merged
}

func main() {
old := []any{
User{ID: 1, Name: "a"},
User{ID: 2, Name: "b"}, // 重複
}
new := []any{
User{ID: 2, Name: "c"}, // 重複
User{ID: 3, Name: "d"},
}

mergedUsers := MergeSlices(old, new, func(item any) any {
return item.(User).ID
})
fmt.Printf("Merged Users:%+v", mergedUsers) // Merged Users:[ {ID:1 Name:a} {ID:2 Name:c} {ID:3 Name:d}]
}

· 3 min read
wen

背景和需求

我在写博客或者周报的时候,一般会这样管理我的分支:

  • 1). 创建带日期的分支,比如 blog/2024-01-28, weekly/2024-01-28
  • 2). 然后在这个分支上写好博客或者周报。
  • 3). 创建 PR,合并到 main 分支。
  • 4). 合并到 main 分支后,会触发 CD 自动部署。

步骤 3) 很容易忘记,所以我想通过 GitHub Actions 来自动化这个过程。

方案

创建一个 Github Actions 的 Workflow,每天定时检查分支名,

发现有当天的日期的分支 weekly/xxxx-xx-xx 的时候,自动创建 PR 并合并到 main 分支。

实现

准备工作

  1. 在 Github 中创建一个 Personal Access TokenPAT),用于创建和合并 PR。(BTW,到期时间最大只能设置 2 年) 1.1. 设置 PAT 的仓库权限: Repository accessOnly select repositories → 选择你的仓库。 1.2. 设置 PAT 的仓库访问权限: PermissionsRepository permissionsimg
  2. 在仓库中创建一个 secret,用于存放 Personal Access Token。

创建 Workflow

在仓库中创建 .github/workflows/merge-pr.yml 文件,内容如下:

name: Merge Weekly Posts Branches
on:
schedule:
- cron: "00 00 * * *"
workflow_dispatch:

jobs:
merge_weekly_branch:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Git
run: |
git config --global user.email "[email protected]"
git config --global user.name "GitHub Actions"

- name: Create and merge PR
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} # Personal Access Token
run: |
# Get the current date in the format 'YYYY-MM-DD'
current_date=$(date +%Y-%m-%d)

# Construct the branch name
branch_name="weekly/${current_date}"
# Fetch the branch to ensure it exists locally
git fetch origin ${branch_name}

# Check if the branch exists
if [[ -n "$(git ls-remote origin $branch_name)" ]]; then
git checkout ${branch_name}
# Create a pull request using GitHub CLI
gh pr create --base main --head $branch_name --title "weekly post PR" --body "This is a PR for weekly post changes."

# Wait for a few seconds to allow GitHub to process the PR creation
sleep 15
# Merge the pull request using GitHub CLI
gh pr merge --squash
else
echo "Weekly branch '${branch_name}' does not exist."
fi

· One min read
wen

Problem

I was trying to run a Makefile in VSCode and I got the following error:

Makefile:4: *** missing separator.  Stop.

Solution

The problem is that the Makefile is using tabs instead of spaces. To fix this, you can:

  1. Open the Makefile in VSCode
  2. Open the command palette with Ctrl + Shift + P or View > Command Palette. If you are using a Mac, you can use Cmd + Shift + P
  3. Search for Convert Indentation to Tabs

If you want to prevent this from happening in the future, you can:

  1. Open settgings in VSCode with Ctrl + , or File > Preferences > Settings. If you are using a Mac, you can use Cmd + ,
  2. Search for Insert Spaces
  3. Uncheck the box for Editor: Insert Spaces

img

· 3 min read
wen

Problem

It's a common problem to count the number of (visible) characters in a string.

In Golang, we can use utf8.RuneCountInString() function to count the number of characters in a string.

It works well for most cases, including multi-byte characters like Chinese.

Code example

package main

import (
"fmt"
"unicode/utf8"
)

func main() {
str := "abc世界"
fmt.Println(utf8.RuneCountInString(str)) // 5
}

But if the string contains emoji characters like 👉🏻, then in some cases this function will not calculate correctly.

package main

import (
"fmt"
"unicode/utf8"
)

func main() {
emojiWorld := "🌍"
fmt.Println(utf8.RuneCountInString(emojiWorld)) // 1 ✅ no problem

emojiHand := "👉"
fmt.Println(utf8.RuneCountInString(emojiHand)) // 1 ✅ no problem

emojiHandBlack := "👉🏿"
fmt.Println(utf8.RuneCountInString(emojiHandBlack)) // 2 ❌ Got 2, expected 1.

emojiOne := "1️⃣"
fmt.Println(utf8.RuneCountInString(emojiOne)) // 3 ❌ Got 3, expected 1.
}

You can copy the emoji from here emojipedia.

Why

This is because some emoji are composed of multiple unicode characters (Code Points), while the utf8.RuneCountInString() function only counts the number of unicode characters.

TermDescription
BytesThe smallest unit used to measure data storage, typically 8 bits in binary.
Code UnitsFixed-size units used in encoding schemes to represent a character. In UTF-8, a Code Unit is 8 bits, and in UTF-16, it's 16 bits.
Code PointsIn the Unicode standard, each character is assigned a unique code point, which is a numerical identifier for the character. For example, the code point for the Latin letter "A" is U+0041.
Grapheme ClustersRepresent the smallest units perceivable in a language, typically a sequence of one or more Code Points. For instance, a letter with an accent mark might be a Grapheme Cluster.

For example, 1️⃣ this emoji is composed of 3 Code Points, which are:

img

Rendered by Markdown Table

tip

Emoji Code Points can be found here emojipedia.

Solution

So we need to count the number of Grapheme Clusters instead of Code Points.

Use third-party library rivo/uniseg

package main

import (
"fmt"

"github.com/rivo/uniseg"
)

func main() {
emojiWorld := "🌍"
fmt.Println(uniseg.GraphemeClusterCount(emojiWorld)) // 1 ✅ 没有问题

emojiHand := "👉"
fmt.Println(uniseg.GraphemeClusterCount(emojiHand)) // 1 ✅ 没有问题

emojiHandBlack := "👉🏿"
fmt.Println(uniseg.GraphemeClusterCount(emojiHandBlack)) // 1 ✅ 没有问题

emojiOne := "1️⃣"
fmt.Println(uniseg.GraphemeClusterCount(emojiOne)) // 1 ✅ 没有问题
}
tip

Actually, there are not only emoji, but also some Thai and Arabic characters are composed of multiple unicode characters.

Reference

Go で文字数をカウントする 在 Go 中计算字符数

文字数をカウントする 7 つの方法

Go: Unicode と rune 型

· One min read
wen

背景

labstack/gommon 的代码中 看到了这个库 valyala/fasttemplate, 于是就去 调查了一下。

tip

labstack 是 Echo Web Framework 的 Organization。

什么是 fasttemplate

fasttemplate 是一个高效的 Go 模板引擎,比 Go 标准库的模板引擎 text/template 快很多,

而且比 strings.Replace, strings.Replacerfmt.Fprintf 都要快。

img

具体可以看一下 fasttemplate 的 benchmark

fasttemplate 的使用

基础用法

    template := "http://{{host}}/?q={{query}}&foo={{bar}}{{bar}}"
t := fasttemplate.New(template, "{{", "}}")
s := t.ExecuteString(map[string]interface{}{
"host": "google.com",
"query": url.QueryEscape("hello=world"),
"bar": "foobar",
})
fmt.Printf("%s", s)

// Output:
// http://google.com/?q=hello%3Dworld&foo=foobarfoobar

高阶用法

    template := "Hello, [user]! You won [prize]!!! [foobar]"
t, err := fasttemplate.NewTemplate(template, "[", "]")
if err != nil {
log.Fatalf("unexpected error when parsing template: %s", err)
}
s := t.ExecuteFuncString(func(w io.Writer, tag string) (int, error) {
switch tag {
case "user":
return w.Write([]byte("John"))
case "prize":
return w.Write([]byte("$100500"))
default:
return w.Write([]byte(fmt.Sprintf("[unknown tag %q]", tag)))
}
})
fmt.Printf("%s", s)

// Output:
// Hello, John! You won $100500!!! [unknown tag "foobar"]

· 3 min read
wen

背景&目的

项目中使用的第三方服务(Shopify)的 API 隔几个月就会发布新版本。

为了及时对应 API 变化,我们希望在该服务的 API 的 RSS 有更新时,能够及时收到通知。

但是 Lark(飞书)并没有提供 RSS 订阅的功能,所以我们需要通过其他方式实现。

实现方法

通过 Zapier 将 RSS 新文章通知发送到 Lark(飞书)Webhook。

Zapier 是一个在线的自动化工作流程工具,可以将不同的应用程序连接起来,实现自动化工作流程。

实现步骤

0. 前提条件

  • Lark(飞书)中创建一个接受通知的 Group,并设置 Webhook,用于接收通知。

1. Zapier 中创建一个 Zap

分 2 步:

  • 1️⃣ 添加 RSS by Zapier, 填入 Shopify API RSS 地址, 用于获取 RSS 新文章通知。(图 1)

  • 2️⃣ 添加 Code by Zapier, 通过编写代码将 RSS 新文章发送到 Lark(飞书)Webhook。(图 2)

    • 如果是付费用户的话,可以使用 Webhook by Zapier 替代 Code by Zapier,省得写代码。
    • 代码语言支持 JavaScript 和 Python,这里用的是 JavaScript。

图 1 图 1 图 2 图 2

图 2 中的设置如下:

  • 1️⃣ 添加变量 Webhook,用于存放 Lark(飞书)Webhook 地址。
  • 2️⃣ 添加变量 Data,用于存放 RSS 新文章通知。 具体存放什么东西可以自己选择,这里存放的是 TitleLink等。
  • 3️⃣ 代码如下:
// 获取设置的变量
let Webhook = inputData.Webhook.trim();
let Method = "POST";

// 发送给 Webhook 的 JSON 对象 (注意格式要符合 Lark(飞书)Webhook 的要求)
let JSONObject = {
msg_type: "text",
content: { text: inputData.Data },
};

// creates the Method, Headers, and Body of the HTTP POST Request
let Options = {
method: Method,
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(JSONObject),
};

const Request = await fetch(Webhook, Options); // HTTP POST Request
const Response = await Request.json(); // HTTP POST Response

output = { Response, Request, Webhook, Method };

References

Make a HTTP POST Request to Fire a Webhook with Headers and Parameters via a Zap Code Step

· 2 min read
wen

0. Manim 是什么?

Manim 是一个用于创建数学动画的库,它是由 3Blue1BrownGrant Sanderson @X开发的。

Manim 的目标是提供一个用于创建数学动画的简单、快速且强大的工具。

它是一个用 Python 编写的开源项目,它的代码托管在 GitHub 上。

1. 最简单快速的安装方法: 利用 GitHub Codespaces

1). 打开(或者 fork)我配置好的仓库 wifecooky/manim-devcontainer,

1️⃣ 点击右上角的 Code 按钮, 2️⃣ 选择 Open with Codespaces,等待构建完成后,就可以在浏览器中使用 Manim 了。

img

2). 测试一下

1️⃣ : 打开 example_scenes.py 文件。

2️⃣ : 在 terminal 运行 manim -ql example_scenes.py SquareToCircle

3️⃣ : 等待运行完成,就可以在 media/videos 目录下看到生成的视频了。

img

2. 本地最快速的安装方法: 利用 Docker

0). 安装 Docker

已经安装过的请跳过这一步。

1). 拉取 Manim 镜像

docker pull manimcommunity/manim

2). 使用 Docker 启动本地 JupyterLab

docker run -it -p 8888:8888 manimcommunity/manim jupyter lab --ip=0.0.0.0

3). 打开 JupyterLab 链接并创建一个新的 Notebook

img

img

4). 测试一下

1️⃣ : 在 Notebook 中运行以下代码

%%manim -qm -v WARNING SquareToCircle

class SquareToCircle(Scene):
def construct(self):
square = Square()
circle = Circle()
circle.set_fill(PINK, opacity=0.5)
self.play(Create(square))
self.play(Transform(square, circle))
self.wait()
tip

%%manim 是 Manim 的 Jupyter magic 命令,用于链接 Binder 来运行 Manim。

-qm 参数表示 quality medium,即生成的视频质量为中等。

-v WARNING 参数表示只显示警告信息。

img

Reference

Manim easy Installation for all operating systems (Windows, Linux, Mac OS)

docker manimcommunity/manim

· One min read
wen

Problem

Docusaurus(3.0) 的文档侧边栏默认是按文件的创建日期的升序排列的。

Docuaurus v3 's docs sidebar is sorted by the creation date of the file by default.

It's not very convenient for me to manage and find docs, so I want to reverse the order of the sidebar items.

img

Solution

Modify the docusaurus.config.js file as follows:

docusaurus.config.js
+// Reverse the sidebar items ordering (including nested category items)
+function reverseSidebarItems(items) {
+ // Reverse items in categories
+ const result = items.map((item) => {
+ if (item.type === 'category') {
+ return {...item, items: reverseSidebarItems(item.items)};
+ }
+ return item;
+ });
+ // Reverse items at current level
+ result.reverse();
+ return result;
+}

/** @type {import('@docusaurus/types').Config} */
const config = {
title: 'thewang',
...
presets: [
[
'classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
routeBasePath: 'weekly',
sidebarPath: require.resolve('./sidebars.js'),
showLastUpdateTime: true,
showLastUpdateAuthor: true,
sidebarCollapsed: false,
+ async sidebarItemsGenerator({defaultSidebarItemsGenerator, ...args}) {
+ const sidebarItems = await defaultSidebarItemsGenerator(args);
+ return reverseSidebarItems(sidebarItems);
+ return sidebarItems;
+ },
},
...

References

Customize the sidebar items generator

· One min read
wen

问题

当你在同一台电脑上使用多个 Git 用户的时候,你可能会遇到在 commit push 之后才发现自己没有切换到正确的用户的问题。

为了避免这种情况,我们可以在终端显示当前的 Git 用户信息。

img

解决方案

终端的显示信息(Shell Prompt) ,我推荐 Starship 配置来实现。

安装 Starship 可以参考官方文档, 这里就不再赘述。

配置

安装完后在 Starship 的配置文件 ~/.config/starship.toml 中添加以下配置, 格式可以按照自己的喜好修改 format = 部分 。

~/.config/starship.toml
format = """
...
${custom.git_username}\
...

[custom.git_username]
command = "git config user.name"
when = "[ -d .git ] && echo .git || git rev-parse --git-dir > /dev/null 2>&1"
format = ' [$symbol($output)@git]($style) '