(总结)开发 org-zettel-ref-mode 的一点经验

最新发布的 org-zettel-ref-mode (以下简称 OZR)版本号从 0.3.3 跳到 0.4,在这一轮冲刺中,我实现了比之前更加复杂的功能,更多的代码行数,更加复杂的结构。而所花费的时间,是过去的三分之一,我认为自己取得了可喜的进步。

OZR 新的版本,增加了一个新功能,用于管理引用来源文件的可视化面板。这句话看上去很简单,但背后的工作量并不小。前端改一个词,足以让后端跑断腿。出现产品经理被砍这样的新闻,也不必奇怪了。

可视化面板,是很多产品里常见的特性,它的功能就是,让用户更加舒服地查看、管理特定的对象。在 OZR 里,其管理对象是,一个个被充当引用来源的参考文件。

如果让你来设计这个面板,你觉得第一个问题会是什么?我一开始的反映是,文件应该以什么样的方式进行展示?但实际上,在我真正着手开发时,真正重要的问题是: 在面板里展示的数据源来自哪里? 在数据源发生了改动之后,如何确保原来的文件映射关系不变?

继续追问这两个问题,会发现,从工程上需要实现:

  1. 将原本非格式化的数据转换成格式化数据,保存到数据库中,面板访问数据库才能既快又好地展现文件信息
  2. 由于用户会在面板上编辑文件信息,因此面板上更新的数据,需要同步到数据库对应的字段中,进行信息层面的对齐,这样子才能确保面板在重新加载之后不会出现错误的信息

从这 2 个问题继续追问,就会出现大量需要实现的工程细节。但最重要的一个问题是,需要使用一个怎么样的数据库,才能够满足以上需求。再深挖这个问题,就是数据库应当使用什么结构,能够满足当前的业务需求?再追问这个问题,就会回到业务层面上,实际业务的情景是什么?

设计数据库之前需要先探明业务情景

在没弄清楚实际业务发生的情形之前,我所尝试的各种实现数据库的方法,都是错的,各种 Bug 层出不穷,按下葫芦浮起瓢。最后我冷静下来,深究原因——保存与存取,总是出现问题。我得感谢之前开发的经验里,让我牢牢记住一个原则:一个 Bug 如果修改 3 次都不成功,一定要转换思路,不要继续重复原来的方法。

所以,我和 AI 对话,对实际的业务情景进行分析,寻找最适合的数据库结构。这个问题决定了数据库的读写方法——

我: overview-id 一定对应一个 ref-id (必需关系) 也未必会有对应的一个 ref-id,因为有的 overview 文件不是从 ref 源文件夹中的文件创建。不过,从严格意义上来讲,一个 overview 文件,必须对应有一个 source 文件

AI: 明白了,让我重新梳理这个关系: 从数据库角度: ref-id 可以没有对应的 overview-id overview-id 可以没有对应的 ref-id 但如果存在映射,则是一对一的关系 从业务逻辑角度: overview 文件必须对应一个 source 文件(业务规则) 但这个对应关系可能还未在数据库中建立(数据状态) 映射关系的建立是一个渐进的过程 Map 表的职责: 记录已建立的映射关系 确保映射的一对一性 不强制要求所有实体都必须有映射 但提供检查机制发现"孤立"的 overview 这样的理解更准确了吗?

我: 在我们的场景里,一个 ref-id 对应一个 overview-id。

AI: 啊,抱歉我理解错了。是的,在我们的场景中是一对一的关系。 让我们重新分析 Map 表的工作机制: 一对一映射的特点: 每个 ref-id 只能对应一个 overview-id 每个 overview-id 也只能对应一个 ref-id 这意味着映射是双向唯一的 Map 表的约束: 创建新映射前需要检查 ref-id 是否已有映射 创建新映射前需要检查 overview-id 是否已被映射 更新映射时需要保证双向唯一性 删除映射时需要同时解除双向关联 查询优化: 可以通过 ref-id 直接找到对应的 overview-id 可以通过 overview-id 反查对应的 ref-id 不需要复杂的多重映射处理 这样的理解更准确吗

我: 场景是这样的,新增加的 ref-id 未必会有对应的 overview-id。

AI: 啊,明白了。这是一个可选的一对一关系:

  1. 映射的特点:
  2. ref-id 可以没有对应的 overview-id (可选关系)
  3. 但如果有映射,则是一对一的
  4. overview-id 一定对应一个 ref-id (必需关系)
  5. Map 表的约束:
  6. 不是所有 ref-id 都需要在 Map 表中
  7. 创建映射时只需检查 overview-id 是否已被使用
  8. overview-id 不能重复使用
  9. ref-id 已有映射时,新映射会替换旧映射
  10. 业务场景:
  11. 新建 ref 时可以不立即创建 overview
  12. 可以后续再为 ref 关联 overview
  13. 可以更换 ref 对应的 overview
  14. overview 必须关联到某个 ref

这样的理解更符合实际场景了吗?

我: overview-id 一定对应一个 ref-id (必需关系) 也未必会有对应的一个 ref-id,因为有的 overview 文件不是从 ref 源文件夹中的文件创建。不过,从严格意义上来讲,一个 overview 文件,必须对应有一个 source 文件

AI: 明白了,让我重新梳理这个关系:

  1. 从数据库角度:
  2. ref-id 可以没有对应的 overview-id
  3. overview-id 可以没有对应的 ref-id
  4. 但如果存在映射,则是一对一的关系
  5. 从业务逻辑角度:
  6. overview 文件必须对应一个 source 文件(业务规则)
  7. 但这个对应关系可能还未在数据库中建立(数据状态)
  8. 映射关系的建立是一个渐进的过程
  9. Map 表的职责:
  10. 记录已建立的映射关系
  11. 确保映射的一对一性
  12. 不强制要求所有实体都必须有映射
  13. 但提供检查机制发现"孤立"的 overview

经过几轮对话之后,数据库所面对的场景,才真正的抽象出来。

实际开发时通过测试用例来 Debug

由于数据库设计的操作复杂,又是读又是取,又是加载又是保存,加上需要从数据库中读取数据,保存数据的函数很多。因此,按照以往,直接修改函数很容易遗漏。

最佳实践是,通过测试用例,来找到还没修改和完善的地方。Emacs 自带 ert 这个测试框架,可以很完善的测试各种功能。有了测试用例配合测试,就可以非常清楚地知道问题出在哪里,直接检查对应的函数。节省了大量的时间,和精力——在 Debug 过程中,最痛苦的不是改 Bug 而是找到 Bug 的源头。

3 个赞

大佬这个项目用的是什么数据库,我没看到sql,是键值数据库nosql吗?

没,打蚊子用不着大炮,就是哈希表。在哈希表里定义了数据结构。

请问一下PDF里的IMAGE默认就支持导出吗? 我现在REQUIREMENTS里除了ebooklib没安装之外,其它包都已经安装好,问题是带图片的pdf导出到org里,没有image

这个有点难实现,我之前试过,但使用的第三方服务,在图片识别上很差,我后来就算逑了,用了比较简单的 pdf2txt(好像叫这个)来转。

所以不会导出图片…

有机会测试别的服务看看。

搜了一下,目前只找到一个在线的extractpdf.com

关于这个包的开发细节我没有去了解过,只说一下我自己的开发经验,可能也是大家耳熟能详的内容,但在具体的开发中深刻的理解并实践可以极大的降低编程的复杂度。

最重要的一点:模块化和分层设计,大体意思就是一个模块或一层只对外部提供少量的,易于使用的函数。比如对数据库的操作就是一个典型例子,不和业务代码混在一块可以降低复杂度,底层优先提供通用的功能,在上层再对其具体化。

其次,模块要够深。所谓够深就是提供的功能和封装的细节够多,但只对外部提供简单的接口。我觉得没有必要为了所谓的极致的通用,把一块代码拆分为许多的小函数,这些小函数只有在许多地方被用到,才值得被单独抽离出来。一大段代码页未尝不可,只要其表达的是一个独立完整的功能,且没有大量的参数透传,合理的函数命名,无脑使用就好,这样是一个好的接口设计。

pdfimages这个实测可以,是个命令行工具,速度对一般论文也还可以 https://www.cyberciti.biz/faq/easily-extract-images-from-pdf-file

如果整合的话,一些个人想法:

  1. 想办法过滤掉一些从pdf页眉等地方扫到的无用image, 比如默认扫出image文件大小小于10KB?
  2. 一般论文的图形说明都是固定的,比如Fig. 1等,在已转的org文档里扫到后可以默认加上image文件链接

一直感觉这个包很不错,昨天开始正式尝试。配置完还没有正式使用,但已经影响我之前的 org roam 了。我在建立一个org roam 笔记后,用 dired rename,出现提示:

org-zettel-ref-db–around-dired-do-rename: Wrong type argument: stringp, (“c:/Users/…/org-roam/bibnote”)

而后面这个路径,是我在配置中设置的

(setq org-zettel-ref-overview-directory (list bibnote-dir))

按说我这个操作跟 ozr 没有关系,是 ozr 监控了常规 org roam 或者 dired 的行为。

之所以在这里发,是因为我觉得开发可能还要兼顾对其他交互的影响最小化。希望 @yibie 大牛能看看修复下。

我上午的时候,把 dired 监视函数给移除了,你更新一下看看

它是只能用于 linux 环境下吗?

我只在linux里试过

不过这个似乎支持windows

先 mark

这个好了,但第一步我用 M-x org-zettel-ref-run-python-script 想把一个 md 文档转换为 Org 就失败了,提示

INFO:__main__:Using venv at: d:\pinadev\.config\emacs\.local\straight\repos\org-zettel-ref-mode\.venv
ERROR:__main__:Python executable not found at d:\pinadev\.config\emacs\.local\straight\repos\org-zettel-ref-mode\.venv\Scripts\python.exe
ERROR:__main__:Virtual environment setup failed, exiting...

我在 windows 下用 scoop 装的 miniconda,emacs 下 beancount 调用 python 啥的都没有问题。看了 .venv 里面有个 pyvenv.cfg,里面的配置貌似都是用户需要配置的。但没有相关文档说需要如何配置。

另外这个 convert-to-org.py 如何单独调用?只能从源码看出来几个参数,但没有进一步的 docstring 来详细说明。

可以单独调用。我没 windows,所以不知道在 windows 下有什么问题。如果你能帮忙解决,其它使用 orz 的朋友们会感谢你的。

脚本本身是可以单独调用的,类似的命令如下,需要填写 3 个路径:

python convert-to-org.py --temp ~/Documents/temp_convert/ --reference ~/Documents/ref/ --archive /Volumes/Collect/archives

我意识到我只需要将 md 转换为 org, 而这个是用 pandoc 实现的,我就改用 pandoc 直接转换了。

我是用 MinerU 进行识别并转 pdf 到 md 的,所以这个脚本看来可以不用了。

另外我前面单独调用 convert-to-org.py 也还是提示 venv 的问题,再看 repo 里 venv 的 pyvenv.cfg, 内容都是写死路径的,恐怕需要修改,如:

executable = /Users/chenyibin/miniconda3/bin/python3.12

其实可以直接把 .venv 直接去掉看看?包含这个环境不是我的本意,纯粹是不知道为何设置了 .gitignore 也一样会上传上去…

我已经把仓库里的 .venv 删除了。