有时,让用户和 Lisp 程序都能通过自定义格式控制字符串来控制特定文本的生成方式会很实用。例如,一个格式字符串可以定义如何显示某人的名、姓和电子邮箱地址。使用上一节介绍的 format 函数,该格式字符串可能形如 "%s %s <%s>"。但这种方式很快会变得不实用 — 因为无法直观区分每个格式说明符对应哪一项信息。
针对这类场景,更方便的格式字符串可以写成类似 "%f %l <%e>" 这样的形式,其中每个格式字符都带有更明确的语义信息,并且可以很方便地与其他格式字符重新排列,让用户更容易自定义这类格式字符串。
本节介绍的 format-spec 函数实现了与 format 类似的功能,区别在于它可以处理使用任意格式字符的格式控制字符串。
该函数根据 spec-alist 中指定的转换规则,从格式字符串 template 生成并返回一个字符串。spec-alist 是一个关联列表(alist,see 关联列表),其形式为 (letter . replacement)。格式化最终字符串时,template 中每个形如 %letter 的格式说明符都会被对应的 replacement 替换。
template 中除格式说明符外的其他字符(包括其可能附带的文本属性)会直接复制到输出结果中;格式说明符自身的任何文本属性也会复制到其对应的替换内容上。
使用 alist关联列表 指定转换规则具备以下实用特性:
替换内容(REPLACEMENT) 也可以是一个无参函数,该函数返回用于替换的字符串。仅当 TEMPLATE 中使用了对应的 字母(LETTER) 时,这个函数才会被调用。例如,这一特性可用于避免在不需要时触发输入提示。
可选参数 ignore-missing 用于指定如何处理 template 中存在但 spec-alist 中未找到的格式说明符字符:
若为 nil 或省略该参数,函数会抛出错误;
若为 ignore,这些格式说明符会原样保留在输出中(包括其可能的文本属性);
若为 delete,这些格式说明符会从输出中移除;
其他非-nil 值的处理方式与 ignore 相同,但所有 ‘%%’ 也会原样保留在输出中。
若可选参数 split 为非-nil,format-spec 不会返回单个字符串,而是根据替换操作的执行位置,将结果拆分为字符串列表。例如:
(format-spec "foo %b bar" '((?b . "zot")) nil t)
⇒ ("foo " "zot" " bar")
format-spec 接受的格式说明符语法与 format 类似,但并非完全相同。两种函数中,格式说明符均是以 ‘%’ 开头、以字母(如 ‘s’)结尾的字符序列。
与 format 不同(format 会为固定的格式说明符字符集赋予特定含义),format-spec 接受任意格式说明符字符,且对所有字符一视同仁。例如:
(setq my-site-info
(list (cons ?s system-name)
(cons ?t (symbol-name system-type))
(cons ?c system-configuration)
(cons ?v emacs-version)
(cons ?e invocation-name)
(cons ?p (number-to-string (emacs-pid)))
(cons ?a user-mail-address)
(cons ?n user-full-name)))
(format-spec "%e %v (%c)" my-site-info)
⇒ "emacs 27.1 (x86_64-pc-linux-gnu)"
(format-spec "%n <%a>" my-site-info)
⇒ "Emacs Developers <emacs-devel@gnu.org>"
格式说明符中,‘%’ 后可紧跟任意数量的以下标志字符,用于修改替换行为的相关特性。
该标志会使宽度指定的填充字符从空格变为 ‘0’;
该标志会使宽度指定的填充字符插入在右侧而非左侧;
若指定了宽度和精度,该标志会将替换内容从左侧截断至指定的宽度和精度;
若指定了宽度和精度,该标志会将替换内容从右侧截断至指定的宽度和精度;
该标志会将替换后的文本转换为大写(see Lisp 中的大小写转换);
该标志会将替换后的文本转换为小写(see Lisp 中的大小写转换)。
使用相互矛盾的标志(例如同时指定大小写转换)的结果是未定义的。
与 format 一致,格式说明符中可包含:
宽度:紧跟在所有标志后的十进制数字;
精度:紧跟在所有标志和宽度后的、以小数点 ‘.’ 开头的十进制数字。
若替换内容的字符数少于指定宽度,会在左侧填充字符:
(format-spec "%8a is padded on the left with spaces"
'((?a . "alpha")))
⇒ " alpha is padded on the left with spaces"
若替换内容的字符数多于指定精度,会从右侧截断:
(format-spec "%.2a is truncated on the right"
'((?a . "alpha")))
⇒ "al is truncated on the right"
以下是一个结合了上述多个特性的复杂示例:
(setq my-battery-info
(list (cons ?p "73") ; Percentage
(cons ?L "Battery") ; Status
(cons ?t "2:23") ; Remaining time
(cons ?c "24330") ; Capacity
(cons ?r "10.6"))) ; Rate of discharge
(format-spec "%>^-3L : %3p%% (%05t left)" my-battery-info)
⇒ "BAT : 73% (02:23 left)"
(format-spec "%>^-3L : %3p%% (%05t left)"
(cons (cons ?L "AC")
my-battery-info))
⇒ "AC : 73% (02:23 left)"
如本节示例所示,format-spec 常用于选择性格式化各类不同信息。这一特性在支持用户自定义格式字符串的程序中尤为实用 —— 用户可通过常规语法,按任意期望的顺序,仅格式化程序提供的部分信息。