在前面我们介绍了 make-process 创建异步进程并且通过 :filter 设置数据过滤。今天我们来讨论它的另一个关键字 :sentinel 。并且现在很多第三方工具输出都是采用json进行,特别是一些网络的api接口。因此我们需要额外掌握Emacs中是如何处理json数据的
背景知识
:sentinel 关键字
这个关键字我们在前面的练习中已经使用了。它的回调函数接受两个参数: 进程对象本身、和状态描述字符串。我们在前面的例子中并没有直接使用第二个参数来判断,反而使用 process-status 来判断状态,因为字符串是给人看的,关于结束它有众多的情况:finished\n 、 exited abnormally with code exitcode\n Emacs并没有一个统一的规范。而 process-status 返回的则是专门给程序使用的状态符号。所以各位读者日后在使用时需要注意,第二个参数是用来给用户提示用的,不能使用它来判断状态。
当确认进程结束之后可以使用 process-exit-status 来判断进程的退出代码。一般情况下成功退出返回0。这就给我们提了一个醒:为了自己的程序具有更好的兼容性,要确保正常退出的退出码是0。否则一些上层集成的工具还需为你的程序专门写判断逻辑。
json 集成
关于 :sentinel 的内容比较简单,而且我们前面已经演示过用法,这里就不再多啰嗦了。
Emacs中关于将json转化为elisp对象,有两种接口,它们分别是旧版的使用纯elisp实现的;以及基于 C 语言 Jansson 库实现的现代json接口。现在一般推荐使用后者因为效率会高一些。但是之前的接口能兼容早期的Emacs版本,所以这里我们两种都介绍一下,方便读者在遇到一些老版本的插件时能理解。
基于elisp 的json接口为:
json-read从当前缓冲区中读取json-read-from-string从字符串中读取
它们的参数都比较简单, json-read-from-string 只有一个json字符串的参数。而 json-read 是从当前缓冲区读取,没有读取指定缓冲区的功能。
基于 Jansson 库的接口为:
json-parse-string解析一个json字符串json-parse-buffer从当前缓冲区的point位置往后读取一个字符串进行解析json-serialize/json-insert用于将lisp 对象序列化为json字符串
json-parse-string 和 json-parse-buffer 的参数类似,主要用来指定json对象转化为何种lisp数据类型:
:object-type指定 JSON 对象转为什么 Lisp 结构,可以选'alist、'hash-table、:plist:array-type指定json中数组转化为何种 lisp结构,可以选'list、array:null-object指定使用哪个lisp对象表示json中的 null:false-object指定使用哪个lisp对象表示json中的 false
json-serialize 和 json-insert 的区别在于二者输出的目标不同,前者输出到一个指定的字符串中(返回一个字符串)。后者直接将内容插入到当前buffer中point位置之后
练习
有了这个我们对前面的编译程序进行一个改造。我们将 make-process 中的关键字使用 json 进行序列化。这样在调用时可以由用户轻松的控制编译版本和行为。例如我们之前一直在编译msvc版本,我们可以将命名稍作修改以便编译 android 、linux、macos 等其他版本。这里我们设计的json 配置如下:
{
"error": "error-buffer",
"std-output": "compile-buffer",
"config": {
"command": ["cmake" "." "-B" "build"
"-G" "Visual Studio 18 2026"
"-T" "v143"
"-A" "x64"
"-DCMAKE_BUILD_TYPE=Debug"
"-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebugDLL"]
},
"build": {
"command": ["cmake" "--build" "build"]
}
}(defun my-cmake-build-config(root-dir &optional json-file)
"在 ROOT-DIR 中根据 json 配置文件的路径 JSON-FILE,加载该json并获取数据,默认从 ROOT-DIR 中的 cmake-build.json 中获取"
(interactive "D选择项目根目录:")
(let* ((default-directory (file-name-as-directory root-dir))
(json-file (or json-file
(expand-file-name "cmake-build.json")))
(json nil) ;;json对象
(out-buf nil) ;;标准输出
(err-buf nil) ;;标准错误
(config-proc nil) ;;构建进程
(build-proc nil) ;;编译进程
)
;; 读取json文件
(unless (file-exists-p json-file)
(user-error "找不到json配置文件: %s" json-file))
(with-temp-buffer
(insert-file-contents json-file)
(goto-char (point-min))
(setq json (json-parse-buffer :object-type 'hash-table
:array-type 'list)))
;;获取并创建缓冲区
(setq out-buf (get-buffer-create
(gethash "std-output" json "*compile-build*")))
(setq err-buf (get-buffer-create
(gethash "error" json "*compile-error*")))
;; 初始化标准输出和标准错误缓冲区,启用 compilation-minor-mode
(with-current-buffer out-buf
(erase-buffer)
(compilation-minor-mode t))
(with-current-buffer err-buf
(erase-buffer)
(compilation-minor-mode t))
(message "开始CMake 构建")
(setq config-proc
(make-process :name "cmake-config"
:command (gethash "command" (gethash "config" json))
:buffer out-buf
:stderr err-buf
:noquery t
:sentinel
(lambda (p event)
(when (memq (process-status p) '(exit signal))
(if (zerop (process-exit-status p))
(progn
(message "配置成功,开始编译")
(setq build-proc
(make-process :name "cmake-build"
:command (gethash "command" (gethash "build" json))
:buffer out-buf
:stderr err-buf
:noquery t
:sentinel
(lambda (p1 event)
(when (memq (process-status p1) '(exit signal))
(if (zerop (process-exit-status p1))
(message "构建成功")
(message "构建失败, 查看: %s" (buffer-name err-buf))
(display-buffer err-buf))))))))
(message "CMake 配置失败,查看 %s" (buffer-name err-buf))
(display-buffer err-buf)))))
config-proc))在加载 json 的时候我们通过 with-temp-buffer 创建了一个临时buffer,并通过 insert-file-contents 将json文件中的数据读取到临时buffer中,随后我们通过 json-parse-buffer 并且通过 :object-type 指定json对象转化为hashtable,后续都是使用 gethash 从hashtable中获取对应键下的值。因为 make-process 中 :command 接受的是字符串的数组,所以在转化时我们通过 :array-type 'list 将数组类型转化为list,这样在后续就可以直接使用了。
因为command要区分是构建时期还是编译期,json中通过 config 和 build 将它包裹为子项,所以在取用时我们也需要取两次,第一次取 config 或者 build 中的子项,然后在子项中通过 command 来获取具体的数组内容。
总结
本篇我们讨论了,elisp中处理json的方法。未来如果我们要集成第三方工具,而工具如果使用json那么我们就可以通过上面介绍的方式来获取json数据。
本篇我们介绍的函数有:
process-statusprocess-exit-statusjson-readjson-read-from-stringjson-parse-stringjson-parse-bufferjson-serialize/json-insert
评论 (0)