尽管我最近开始用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中。最终的脚本如下
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| 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') { 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 }); } return qas; }
async function dropFaq() { await request({ method: 'delete', url: 'http://localhost:9200/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中搭建笔记查阅系统的尝试》这篇文章。