尽管我最近开始用VNote做读书或读在线文档的笔记,但更多的时候,我把经验型知识都记录在一个名为my_note
的Git仓库中。这个仓库中有许多.org
文件:
-
TeX.org
,记录与LaTeX
相关的问题和解决方法; -
asm.org
,记录的是与编写汇编语言程序相关的问题和解决办法; -
cl.org
,记录的是与编写Common Lisp代码相关的问题和解决办法;
这些内容被我称为FAQ。尽管不同的文件记载着不同方面的内容,但它们的格式是一致的:
- 每个文件都以org-mode的语法书写;
- 文件中只有一级条目,没有嵌套;
- 每一个条目的标题就是一个问题的表述,下方的文字则是这个问题的答案。
A picture is worth a thousand words
这些问题都比较常见(不然怎么叫FAQ呢——也许上图的不算常见吧),回过头来查找的机率很高。显然,在纷繁复杂的文字中凭肉眼寻找关键字是低效的,即使是祭出grep
,用正则表达式这样的大杀器来查找也不是特别称手——因为并不知道怎样的正则表达式可以匹配到寻找的内容——也许多写了关键词,也许少写了,也许顺序不对。
对于搜索这类非结构的文字资料来说,全文检索是一个更好的选择,因此,我是把这些内容丢进ElasticSearch里再查找的。
解析并导入到ElasticSearch
FAQ中的每一个条目,都对应ElasticSearch中的一个文档,它们都存储在索引faq
中。一个文档有如下的字段:
-
answer
,即问题的答案; -
path
,文件绝对路径,表示文档来自于哪一个文件中的条目; -
question
,即问题的描述; -
questionLineNum
,即问题存在于文件的第几行。
解析这些文件的逻辑也很简单:每当读入行首为星号的一行后(这一行即为问题),便继续读入后续的每一行直到再次遇到行首为星号的行为止,这些后续读入的行组成了这个问题的答案。有了问题和答案,便可以导入到ElasticSearch中。最终的脚本如下
const request = require('co-request');
const fs = require('fs');
function parseFaqOrg(path) {
const content = fs.readFileSync(path).toString('utf-8');
const lines = content.split('\n');
const qas = [];
let answer = [];
let lineNum = 0;
let mode;
let question;
let questionLineNum;
for (const line of lines) {
lineNum += 1;
if (line.startsWith('*')) {
if (mode === 'answer') {
// 在遇到星号的时候模式已经处于answer中,说明在此之前还有未处理的QA
qas.push({
answer: answer.join('\n'),
path,
question,
questionLineNum
});
answer = [];
question = null;
}
mode = 'question';
} else {
mode = 'answer';
}
if (mode === 'answer') {
answer.push(line);
} else {
question = line;
questionLineNum = lineNum;
}
}
if (question) {
qas.push({
answer: answer.join('\n'),
question
});
}
// console.log(JSON.stringify(qas, null, 2));
return qas;
}
async function dropFaq() {
await request({
method: 'delete',
url: 'http://localhost:9200/faq'
});
}
/**
* 重建faq索引并写入全量的笔记数据
*/
async function main() {
console.log(new Date().toLocaleString());
await dropFaq();
const dir = '/Users/liutos/Documents/Projects/my_note/faq/';
const basenames = fs.readdirSync(dir);
for (const basename of basenames) {
const path = dir + basename;
const type = basename.match(/(.*)\.org/)[1];
const qas = parseFaqOrg(path);
for (const qa of qas) {
await request({
body: qa,
json: true,
method: 'post',
url: 'http://localhost:9200/faq/_doc'
});
}
console.log(`文件${path}处理完毕`);
}
console.log(new Date().toLocaleString());
}
main();
后记
如果想知道我是如何在Emacs中查询这些FAQ的,可以参见《在Emacs中搭建笔记查阅系统的尝试》这篇文章。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。