分类目录归档:技术

shallow focus photography of computer codes

Golang 中如何为 XML 加入 CDATA 支持

需求

最近在参与 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

5e54199359bbafe0ef692365a9bcffb6

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)

微信开放社区抓取工具 CHANGELOG

image

描述

这个连接是我自己用的工具的更新 CHANGELOG。如果想要试用,可以私聊我获取压缩包。

需求

  • 定时刷新
  • 气泡提醒
  • macOS 版本

CHANGELOG

0.0.0.4

  • U 抽象出新的函数,优化程序结构
  • U 更新了数据更新的机制,应用程序回归前台以后会自动刷新应用程序

0.0.0.3

  • 完成数据抓取的能力

如何 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 来学习到更多的内容。

Windows Terminal 的 WSL 配置

我在考虑将自己的笔记本从 macOS 转为 Windows,所以就开始研究 Windows 下我常用的软件的替代品,于是,便试了试 Windows Subsystem For Linux ,这一试,发现 WSL 的确不错,很好用,便下载了 Windows Terminal ,作为自己的新的开发环境。

在使用 Windows Terminal 的时候,我遇见了一个问题,我希望在 Windows Terminal 中添加一个新的配置文件,从而让我的 Windows Terminal 在启动的时候就自动进入 Linux 子系统,因此,在互联网上搜索了一些教程后,我将我自己的配置改成了这个样子的。

{
    "globals" :
    {
        "alwaysShowTabs" : true,
        "copyOnSelect" : false,
        "defaultProfile" : "{58ad8b0c-3ef8-5f4d-bc6f-13e4c00f2530}",
        "initialCols" : 120,
        "initialRows" : 30,
        "keybindings" :
        [
            {
                "command" : "closePane",
                "keys" :
                [
                    "ctrl+shift+w"
                ]
            }
        ],
        "requestedTheme" : "system",
        "showTabsInTitlebar" : true,
        "showTerminalTitleInTitlebar" : true,
        "wordDelimiters" : " ./\\()\"'-:,.;<>~!@#$%^&*|+=[]{}~?\u2502"
    },
    "profiles" :
    [
        {
            "acrylicOpacity" : 0.5,
            "background" : "#012456",
            "closeOnExit" : true,
            "colorScheme" : "Campbell",
            "commandline" : "powershell.exe",
            "cursorColor" : "#FFFFFF",
            "cursorShape" : "bar",
            "fontFace" : "Consolas",
            "fontSize" : 12,
            "guid" : "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
            "historySize" : 9001,
            "icon" : "ms-appx:///ProfileIcons/{61c54bbd-c2c6-5271-96e7-009a87ff44bf}.png",
            "name" : "Windows PowerShell",
            "padding" : "0, 0, 0, 0",
            "snapOnInput" : true,
            "startingDirectory" : "%USERPROFILE%",
            "useAcrylic" : false
        },
        {
            "acrylicOpacity" : 0.75,
            "closeOnExit" : true,
            "colorScheme" : "One Half Dark",
            "commandline" : "debian",
            "cursorColor" : "#FFFFFF",
            "cursorShape" : "bar",
            "fontFace" : "Consolas",
            "fontSize" : 11,
            "guid" : "{58ad8b0c-3ef8-5f4d-bc6f-13e4c00f2530}",
            "historySize" : 9001,
            "icon" : "ms-appx:///ProfileIcons/{9acb9455-ca41-5af7-950f-6bca1bc9722f}.png",
            "name" : "Debian Path",
            "padding" : "0, 0, 0, 0",
            "startingDirectory" : "/home/bestony",
            "snapOnInput" : true,
            "useAcrylic" : true
        }
    ],
    "schemes" :
    [
        {
            "background" : "#282C34",
            "black" : "#282C34",
            "blue" : "#61AFEF",
            "brightBlack" : "#5A6374",
            "brightBlue" : "#61AFEF",
            "brightCyan" : "#56B6C2",
            "brightGreen" : "#98C379",
            "brightPurple" : "#C678DD",
            "brightRed" : "#E06C75",
            "brightWhite" : "#DCDFE4",
            "brightYellow" : "#E5C07B",
            "cyan" : "#56B6C2",
            "foreground" : "#DCDFE4",
            "green" : "#98C379",
            "name" : "One Half Dark",
            "purple" : "#C678DD",
            "red" : "#E06C75",
            "white" : "#DCDFE4",
            "yellow" : "#E5C07B"
        }
    ]
}
Code language: JSON / JSON with Comments (json)

这个配置项目中,需要注意的是 profiles.[1].commandline 你会发现,我设置的是 debian 这个和很多教程是不一样的,不少教程使用的是 wsl -d debian 这样的,但是实际上,如果你使用的是 wls -d debian ,你会发现,你配置的 startingDirectory 就会失效,但是如果你的启动命令是 debian, 不会影响你的 startingDirectory 的配置,非常舒服。

Reference

  • https://lwz322.github.io/2019/06/01/Terminal.html
  • https://github.com/microsoft/terminal/issues/1183