4.8 Пользовательские строки форматирования

Иногда полезно позволить пользователям и программам Lisp контролировать, как генерируется определенный текст, с помощью пользовательских строк управления форматированием. Например, строка форматирования может контролировать, как отображать имя, фамилию и адрес электронной почты кого-либо. Используя функцию format, описанную в предыдущем разделе, строка форматирования может выглядеть примерно так: "%s %s <%s>". Однако этот подход быстро становится непрактичным, поскольку может быть неясно, какой спецификатор соответствует какой информации.

Более удобная строка форматирования для таких случаев может выглядеть примерно так: "%f %l <%e>", где каждый спецификатор несет больше семантической информации и может быть легко переупорядочен относительно других спецификаторов, что делает такие строки форматирования более настраиваемыми пользователем.

Функция format-spec, описанная в этом разделе, выполняет аналогичную функцию функции format, за исключением того, что она работает со строками управления форматированием, использующими произвольные спецификаторы.

Функция: format-spec template spec-alist &optional ignore-missing split

Эта функция возвращает строку, полученную из строки форматирования template в соответствии с преобразованиями, указанными в spec-alist, который является alist (см. Списки ассоциаций) вида (буква . замена). Каждый спецификатор %буква в template будет заменен на замена при форматировании результирующей строки.

Символы в template, отличные от спецификаторов форматирования, копируются напрямую в вывод, включая их текстовые свойства, если таковые имеются. Любые текстовые свойства спецификаторов форматирования копируются в их замены.

Использование alist для указания преобразований дает ряд полезных свойств:

  • Если spec-alist содержит больше уникальных ключей буква, чем уникальных спецификаторов в template, неиспользованные ключи просто игнорируются.
  • Если spec-alist содержит более одной ассоциации с одной и той же буквой, используется ближайшая к началу списка.
  • Если template содержит один и тот же спецификатор более одного раза, то же самое замена, найденное в spec-alist, используется в качестве основы для всех подстановок этого символа.
  • Порядок спецификаторов в template не обязательно должен соответствовать порядку ассоциаций в spec-alist.

ЗАМЕНА также может быть функцией, не принимающей аргументов и возвращающей строку, которая будет использоваться для замены. Она будет вызвана только тогда, когда соответствующая БУКВА используется в ШАБЛОНЕ. Это полезно, например, чтобы избежать запроса ввода, если он не нужен.

Необязательный аргумент 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-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’

Этот флаг заставляет любое дополнение, указанное шириной, состоять из символов ‘0’ вместо пробелов.

‘-’

Этот флаг заставляет любое дополнение, указанное шириной, вставляться справа, а не слева.

‘<’

Этот флаг заставляет подстановку обрезаться слева до заданной ширины и точности, если они указаны.

‘>’

Этот флаг заставляет подстановку обрезаться справа до заданной ширины и точности, если они указаны.

‘^’

Этот флаг преобразует подставляемый текст в верхний регистр (см. Преобразование регистра в Lisp).

‘_ (подчеркивание)’

Этот флаг преобразует подставляемый текст в нижний регистр (см. Преобразование регистра в 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")      ; Процент
            (cons ?L "Battery") ; Статус
            (cons ?t "2:23")    ; Оставшееся время
            (cons ?c "24330")   ; Емкость
            (cons ?r "10.6")))  ; Скорость разряда

(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 часто используется для выборочного форматирования набора различных информационных фрагментов. Это полезно в программах, которые предоставляют пользовательские строки форматирования, поскольку пользователь может выбрать форматирование с помощью регулярного синтаксиса и в любом желаемом порядке только подмножество информации, доступной программе.