在 Emacs 中,一切皆可视为服务。
In Emacs, Everything Looks Like a Service

原始链接: http://yummymelon.com/devnull/in-emacs-everything-looks-like-a-service.html

虽然从技术上讲 Emacs 并非操作系统,但它调度应用程序和系统服务的能力,使其成为在一个界面中“生活”的理想环境。通过利用 Emacs Lisp (Elisp),用户可以构建出将外部程序、Web API 和 Shell 工具视为服务的客户端。 客户端-服务器模型是这一范式的核心。Emacs 提供了必要的库来管理用户界面、处理网络通信(客户端边缘)以及管理本地数据。无论是使用原生 Elisp 库处理 JSON,还是通过 Shell 调用来封装现有的命令行工具,开发者都可以用极少的代码实现功能强大且轻量的客户端行为。 `wttr.el` 的实现就是一个典型的例子,它通过获取并解析天气数据,直接在迷你缓冲区(minibuffer)中显示。这展示了 Emacs 如何抽象复杂的交互,使用户能够将几乎所有的计算任务视为一种服务。归根结底,Elisp 的动态特性使得外部工具能够被无缝集成,将 Emacs 转变为一个高度可扩展、统一的计算枢纽,几乎可以满足所有的计算需求。

抱歉。
相关文章

原文

A common refrain is that Emacs is an operating system (OS). This isn’t true, but what invites comparison to an OS is its ability to orchestrate applications and utilities above the OS kernel level. The diagram below suggests a truer picture of how Emacs’ relates to an OS and its capabilities.

img

Emacs’ built-in access to OS system services (file system, network, etc.) coupled with the ability to run other programs makes it routine to improvise client behavior within it. Because of this, Emacs users are able to accomplish many of their computing needs from the different client modes that have been made for it. This gives credence to the notion of “living only in Emacs.”

In this post, we’ll examine some of the ways Emacs lets you build a client. By the end of this post, you’ll hopefully be convinced that from within Emacs, everything looks like a service.

Let’s first provide some definitions.

The Client–Server model is a common computer interaction pattern where a task is partitioned between the provider of a resource (the service) and the requester of that resource (the client). The client issues a request to the server, and the server in turn returns a response as shown in the diagram below.

img

Depending on the implementation, the transaction (request + response) can occur over a network or be local to a system. Client-server models using a network has been most elaborated upon with REST-style software architectures. Shown in the sequence diagram below is a common implementation pattern for REST-style client server architecture.

img

From the diagram above, there are three concerns the client is typically responsible for:

  • UI: User interface (if any).
  • Client Edge: Sub-system concerned with communication with the service. For networked clients, this is the network sub-system.
  • Local Database: Representation of data that is exchanged or synchronized with the server. How this data is managed is up to the implementation requirements.

For the above concerns, Emacs provides numerous libraries both built-in and third-party which can implement a client. Listed below are some built-in libraries with their respective links for further reading:

  • UI

  • Client Edge

  • Local Database

Requirements dictate the amount of complexity required to implement the Emacs client. If there is an existing command line utility that can do the “heavy lifting”, said utility can be reframed as a “service” that can be accessed via a shell call.

img

All the libraries mentioned above are accessed through the Emacs Lisp (Elisp) programming language. Elisp is a dynamic programming language which allows for a high degree of improvisation during run-time. This capability allows for complex orchestration of any behavior that is available to Emacs, from Elisp functions to shell commands.

wttr.in is a console-oriented weather forecast web-service. It supports JSON output so we can build an Emacs wttr command which will prompt for a location, make the HTTP request, process the JSON response and display the result in the mini-buffer.

The top-level command wttr is shown below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
(defun wttr (location)
  "Show weather conditions for LOCATION from `https://wttr.in' in mini-buffer.

Result is also stored in `kill-ring'."
  (interactive "sWhere (default: local): ")

  (condition-case err
      (let* ((url (wttr--request-url location))
             (jsondb (fetch-json-as-hash-table url))
             (msg (wttr--report-message jsondb)))
        (kill-new msg)
        (message "%s" msg))

    (error (message "ERROR: %s" (cdr err)))))

The wttr.in URL is constructed by the function wttr--request-url shown below.

(defun wttr--request-url (location)
  "Construct wttr.in URL with LOCATION."
  (let* ((base-url (url-generic-parse-url "https://wttr.in"))
         (encoded-location (string-replace " " "+" location))
         (query (format "/%s?0&format=j1" encoded-location))
         (_dummy (setf (url-filename base-url) query)))
    (url-recreate-url base-url)))

We can subsequently pass that URL into fetch-json-as-hash-table which does the heavy lifting of retrieving the URL and parsing the JSON response into an Elisp hash-table.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
(defun fetch-json-as-hash-table (url)
  "Fetch URL with expected JSON response and return a `hash-table'."
  (let ((data-buffer (url-retrieve-synchronously url)))
    (if (not data-buffer)
        (error "Failed to fetch data from %s" url)
      (unwind-protect
          (with-current-buffer data-buffer
            ;; Move point past the HTTP metadata headers
            (goto-char url-http-end-of-headers)
            ;; Parse the remaining JSON buffer into a hash-table
            (json-parse-buffer :object-type 'hash-table))
        ;; Always kill the downloaded network buffer to prevent memory leaks
        (kill-buffer data-buffer)))))

Finally we can extract the desired values from the JSON response (jsondb) to populate the message that will sent to the mini-buffer.

 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
(defun wttr--report-message (jsondb)
  "Generate weather report message from JSONDB."
  (let* ((area-buflist ())
         (nearest-area
          (wttr--get-first jsondb "nearest_area"))
         (area-name
          (map-elt (wttr--get-first nearest-area "areaName") "value"))
         (region
          (map-elt (wttr--get-first nearest-area "region") "value"))
         (country
          (map-elt (wttr--get-first nearest-area "country") "value"))

         (current-condition (wttr--get-first jsondb "current_condition"))
         (temp_c (map-elt current-condition "temp_C"))
         (temp_f (map-elt current-condition "temp_F"))

         (weather-description
          (map-elt
           (wttr--get-first current-condition "weatherDesc") "value")))

    (mapc (lambda (x)
            (if (and x (not (string-equal x "")))
                (push x area-buflist)))
          (list area-name region country))

    (format "%s: %s°C, %s°F %s"
            (string-join (reverse area-buflist) ", ")
            temp_c
            temp_f
            weather-description)))

wttr.el source

At this point, hopefully you are convinced of the title assertion that from Emacs, everything looks like a service. Furthermore, many of the APIs offered by Emacs work at a high-level of abstraction. Consider that the lines of code for wttr.el weighs in at 67. (Result using the cloc utility.)

If that’s too much, then imagine an alternate implementation where the actual network request and JSON processing is done in a Python script called weather. Then the Elisp command to invoke it is just the code shown below.

(defun weather (location)
  "Call weather script with LOCATION and show result in minibuffer."
  (interactive "sWhere (default: local): ")

  (let* ((weather-cmd "weather")
         (cmd (if location (format "%s %s" weather-cmd location) weather-cmd))
         (result (shell-command-to-string cmd)))
    (kill-new result)
    (message result)))

With the above implementation, the shell command becomes effectively the “service” to make a request to.

As Elisp is a dynamic programming language, it can allow for integration of Elisp libraries with command line utilities in an improvised fashion.

This capability is compelling to users who recognize the opportunities it can offer.

emacs

联系我们 contact @ memedata.com