cl-кто передает поток в funcalls

Я использую cl-who (через hunchentoot), до сих пор полностью успешно, но есть одна вещь, которую я не могу понять, и мой обходной путь уродлив, поэтому я надеюсь, что это легко исправить. Мои обработчики hunchentoot easy вызывают функции, которые выглядят примерно так:

(defun foo ()
 (with-html-output-to-string
   (*standard-output* nil :prologue t)
   (:html
    (:body (htm :br :hr "foo" :hr ...etc...))))

И все хорошо.

Однако, когда я хочу вызвать вторичную функцию из foo, чтобы выполнить... любую подработу, которую я хочу выполнить, я не могу понять, как заставить HTM-контекст CL-WHO выполнять вызов. Например, это отлично работает:

(defun foo ()
  (with-html-output-to-string
   (*standard-output* nil :prologue t)
   (:html
    (:body (htm :br :hr "foo" :hr (bar)))))

(defun bar ()
   (format t "This will show up in the html stream"))

но это НЕ работает:

(defun bar ()
  (with-html-output-to-string
   (*standard-output* nil :prologue t)
   (htm "This will NOT show up in the html stream")))

(Я пробовал различные манипуляции с этим, безрезультатно.)

Я уверен, что делаю что-то простое неправильно; Ужасно некрасиво возвращаться к формату t в любом subfn, особенно. bcs Я не могу использовать удобные html-макросы cl-who.


person jackisquizzical    schedule 20.12.2018    source источник
comment
Почему бы не просто (defun bar() "This will NOT show up in the html stream")? Вы не делаете ничего, что требует htm.   -  person Barmar    schedule 21.12.2018
comment
Избавьтесь от :prologue t. Это следует использовать только в HTML-документе верхнего уровня, а не внутри других тегов.   -  person Barmar    schedule 21.12.2018


Ответы (2)


CL-WHO основан на макросах, которые генерируют операторы записи, и автоматически печатаются все формы, начинающиеся с ключевого слова, а также значения аргументов. Другие формы только оцениваются (скажем, на наличие побочных эффектов) и не печатаются автоматически. Вот почему CL-WHO вводит макролеты str, fmt, esc и htm, которые заставляют печатать свои аргументы (по-разному).

Ваш код:

(defun bar ()
  (with-html-output-to-string
   (*standard-output* nil :prologue t)
   (htm "This will NOT show up in the html stream")))

Возвращаемое значение представляет собой строку, поскольку вы используете with-html-output-to-string. *standard-output* временно привязан к потоку, отличному от потока за пределами bar, только для создания строки, которая возвращается вызывающей стороне, здесь foo. Строка не печатается (печатаются только формы, которые являются постоянными строками в позиции содержимого).

Вы можете принудительно записать возвращенный сгенерированный HTML-код, используя str, но ИМХО лучший вариант - писать непосредственно в тот же поток вывода, что и вызывающая сторона, вместо создания промежуточных строк.

Прямая запись в поток

В основном используйте with-html-output:

  • Я предпочитаю использовать не *standard-output*, а поток, который используется только для html. Это не позволяет другим библиотекам когда-либо писать что-либо нежелательное на HTML-странице. Вы также можете передать поток каждой вспомогательной функции, но в таких случаях лучше использовать специальные переменные.

  • Давайте использовать простые макросы, чтобы иметь более легкий синтаксис и применять наши собственные соглашения.

Далее определяется пакет и настраивается CL-WHO для создания кода HTML5. Это необходимо сделать до того, как макрос будет раскрыт, так как во время макрорасширения используются специальные устанавливаемые переменные:

(defpackage :web (:use :cl :cl-who))
(in-package :web)

;; Evaluate before CL-WHO macro are expanded
(eval-when (:compile-toplevel :load-toplevel :execute)
  (setf (html-mode) :html5))

Определим поток, которым мы можем управлять, привязанный по умолчанию к тому, что *standard-output* привязано, когда мы открываем поток (а не когда мы определяем переменную):

(defvar *html-output* (make-synonym-stream '*standard-output*)
  "Use a dedicated stream variable for html")

Кроме того, определите общий уровень отступа:

(defvar *indent* 2
  "Default indentation")

Есть два макроса: один для сниппетов, встроенных во вспомогательные функции, которые пишут в наш поток, и один для html-страниц верхнего уровня, которые возвращают строку.

(defmacro with-html (&body body)
  "Establish an HTML context (intended for auxiliary functions)."
  `(with-html-output (*html-output* nil :indent *indent*)
     ,@body))

(defmacro with-html-page (&body body)
  "Return an HTML string (intended for top-level pages)."
  `(with-html-output-to-string (*html-output* nil :prologue t :indent *indent*)
     ,@body))

Пример использования:

(defun my-section (title)
  (with-html
    (:h1 (esc title))
    (:p "lorem ipsum")))

(defun my-page ()
  (with-html-page
    (my-section "title")))

Вызов (my-page) возвращает:

"<!DOCTYPE html>

<h1>title
</h1>
<p>lorem ipsum
</p>"

См. также менее известный https://github.com/ruricolist/spinneret.

person coredump    schedule 20.12.2018
comment
Ах. Итак, я не совсем запутался, как думал; вам действительно нужно сделать это по-другому в auxfn. Спасибо! - person jackisquizzical; 22.12.2018

Мне непонятно, что вы пытаетесь сделать. Если вы хотите составить веб-страницу «кусками», с помощью функций, которые создают части страницы, вы можете использовать str при вызове одной из таких функций, например:

(defun f1 ()
  (with-html-output-to-string (*output-string*)
    (:p "some text")))

(defun f2 ()
  (with-html-output-to-string (*output-string*)
       (:body (:p "some other text") (str (f1)))))

(f2)
"<body><p>some other text</p><p>some text</p></body>"

Из руководства:

Формы, похожие на (str form), будут заменены на (let ((result form)) (when result (princ result s))).

Если вместо этого вы не используете str, результат не будет содержаться в выводе html:

(defun f3 ()
  (with-html-output-to-string (*output-string*)
       (:body (:p "some other text") (f1))))

(f3)
"<body><p>some other text</p></body>"
person Renzo    schedule 20.12.2018
comment
Но также говорится, что строка будет напечатана дословно, поэтому htm должно работать так же. - person Barmar; 21.12.2018
comment
@Barmar, да, но для формы, возвращающей строку, эта строка не будет отображаться в потоке. Я предположил, что постоянная строка была просто примером. - person Renzo; 21.12.2018