《elisp 每日一练》——Process Sentinel 与 json集成

《elisp 每日一练》——Process Sentinel 与 json集成

Masimaro
2026-10-25 / 0 评论 / 1 阅读 / 正在检测是否收录...

在前面我们介绍了 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-status
  • process-exit-status
  • json-read
  • json-read-from-string
  • json-parse-string
  • json-parse-buffer
  • json-serialize / json-insert
0

评论 (0)

取消