用 C# 设置 Hugo Aliases

2020 年新年好!

相信细心的读者已经注意到了,本博客的文章链接已经全部从 blog.batkiz.com/:year/:month/:day/:filename/ 调整为了 blog.batkiz.com/posts/:year/:filename/ 的 url 格式。主要的原因是 url 的可读性:一则我个人认为让完整的日期出现在文章的 url 中对“人”非常之不友好。比如我曾想直接输入文章链接访问文章时,日期极大地为我原本就不太好的记忆力增加了负担。另一方面,具体而完整的时间对于读者也是不必要的,几乎没有人会关注文章是在几月几号几点写的,作为读者,更关注的点在于内容,在 url 中隐藏掉具体的时间,只给出一个年份,在我看来是某种程度上的平衡。

其实这个想法早在将博客生成器由 hexo 切换到 hugo 时就有了,但是由于兼容性的问题(我在煎蛋等平台上的投稿有到我文章的链接),我不能让原本的文章给 404 掉,所以当时只能忍辱负重,继续使用含有完整日期的 url。(见此文

而在某天晚上我刷牙洗漱时,我突然想到,能否将 url 更改的同时,设置以带有完整日期链接的访问跳转到新的链接。经过一番搜索,我找到了名为 Aliases 的功能,这正是我要找的!

但是,在打算用这个功能时,我犯了难:博客经过三年多的积累,文章的量并不是我能够手动一篇一篇的修改的了的。当然啦,作为一个程序员,第一时间想到的,当然就是通过写代码来自动化啦。

通过观察,所添加的 Aliases 项应被添加在 frontmatter 中,其主要依赖了文章的日期即 date 项。我最开始的想法是,将每篇文章的 frontmatter (在我的文章中用的是 yaml)序列化,对序列化后的项进行修改,最后再反序列化,保存至文章。

但是由于我并没有找到一个可用性较高的解析 markdown 文件 yaml frontmatter 的库,此想法只好作罢。

经过我的进一步观察,每篇文章的 date 项均处在第三行,且格式为

1
date: yyyy-mm-dd time

事情就好办了起来。

核心思想即:读入文本,将位于第三行的日期解析出来->生成 Aliases 项->写入文本。

下面是代码(C# 8, dotnet core 3.1),由于时间匆忙,且需求简单,因此未做过多的处理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
using System.Linq;
using System.IO;
using System;

namespace AddAliasToBlogPosts
{
    class Program
    {
        static void Main(string[] args)
        {
            string dir = @"C:\code\blog-contents\content\posts";
            var posts = GetPosts(dir);

            foreach (var item in posts)
            {
                AppendAlias(GetAlias(item), item, 3);
                //System.Console.WriteLine(GetAlias(item));
            }
        }

        static string[] GetPosts(string dir) => Directory.GetFiles(dir);


        static string GetFilesName(string path)
        {
            path = path.Split('\\').Last();
            return path.Substring(0, path.Length - 3);
        }
        static string GetLine(string text, int lineNo)
        {
            string[] lines = text.Replace("\r", "").Split('\n');
            return lines.Length >= lineNo ? lines[lineNo - 1] : null;
        }

        static Func<string, string> GetTime = (path) =>
        {
            string text = File.ReadAllText(path);
            string time = GetLine(text, 3);
            return time.Substring(6, 10).Replace('-', '/');
        };

        static Func<string, string> GetAlias = (path)
             => $"\naliases:\n    - /{GetTime(path)}/{GetFilesName(path)}/";

        static void AppendAlias(string newText, string fileName, int line_to_edit)
        {
            string[] arrLine = File.ReadAllLines(fileName);
            arrLine[line_to_edit - 1] += newText;
            File.WriteAllLines(fileName, arrLine);
        }
    }
}

最后,感谢 MSDN 与 StackOverflow。